Compare commits

...

45 Commits

Author SHA1 Message Date
4b05458cec add all qemu-img convert disk formats 2025-06-13 11:50:39 +02:00
84a0e286e7 support local test-run for dev 2025-06-12 12:54:24 +02:00
58cfaa7d0f add qcow2 and vmdk boot images 2025-06-12 12:20:48 +02:00
1871eac7bb keeping old but still valid CA certs on renewal 2025-01-26 19:28:30 +01:00
b12ce7299f check leaf certificates against their CA 2025-01-26 18:39:31 +01:00
82f7cbcc92 resolve ioutil deprecations 2025-01-26 11:31:04 +01:00
ce8b7f01ef dir2config: introduce #!gen 2024-11-05 11:53:23 +01:00
edbe1641fd chore dockerfile 2024-11-04 22:46:11 +01:00
aac792c341 preliminary multi-net support 2024-08-17 19:13:06 +02:00
eaeb38b8c2 bump default grub-support 2024-04-25 20:31:29 +02:00
e0f755ec42 minor ui fixes 2024-04-19 15:16:31 +02:00
bb7c3835bc fix Dockerfile base image 2024-04-16 11:38:04 +02:00
7c9334233d host from template ui 2024-04-15 16:35:53 +02:00
699b8e71a6 host templates 2024-04-15 15:42:40 +02:00
d4dbe709e0 bump go 2024-03-02 20:52:24 +01:00
22a3e0b6c2 bump dependencies 2024-03-02 11:25:22 +01:00
e08bf0e99d fix login on open store 2024-02-26 11:21:29 +01:00
1e904b7361 ui: fix del-key cancel handling 2024-01-07 11:22:57 +01:00
8ed0f12fb4 fix add key 2024-01-07 11:18:50 +01:00
f59eca6724 use GIT_TAG to override Version 2024-01-07 11:10:45 +01:00
b5b7272603 docker: update & use build cache 2024-01-07 10:56:42 +01:00
4f48866daa dir2config: templates: 'default' fn 2024-01-06 18:20:03 +01:00
b616b710cb hashes passwords support 2023-12-17 17:29:17 +01:00
c02f701c04 ssh: load more host key formats than rsa 2023-12-17 14:40:48 +01:00
7f429a863d clear initrd-v2 flag, use config 2023-11-27 15:46:58 +01:00
29ed01a19f hack: +install +docker-build 2023-11-27 13:52:44 +01:00
07e9dccd06 dir2config: add version 2023-11-25 16:47:20 +01:00
40d08139db public/unlock-store: idempotent call for passphrases
This allows the user to call it even after the store has been unlock in
order to get the admin token.
2023-11-09 08:59:30 +01:00
efa6193954 update Dockerfile to use hack/build 2023-11-07 15:17:47 +01:00
f7b708ce4b add server version/commit in logs and UI 2023-11-04 13:53:00 +01:00
41897c00b4 go mod update 2023-11-04 13:24:57 +01:00
ee5629643c named passphrases (+deletion by name) 2023-09-10 16:47:54 +02:00
34afe03818 cleanup old boot v1 code 2023-08-20 11:13:51 +02:00
25c2d20c19 ui: better ordering 2023-08-19 21:19:50 +02:00
c338522b33 allow store upload, + upload UIs for store and config 2023-08-19 21:17:23 +02:00
b6fa941fcc cleanup 2023-05-22 19:16:11 +02:00
7619998d8f dockerignore more 2023-05-22 19:05:33 +02:00
b6e7c55704 cleanup hosts ws 2023-05-18 19:55:52 +02:00
4ed50e3b78 upgrade dockerfile 2023-05-15 19:34:51 +02:00
dac6613646 fix code vs status in store errors 2023-05-15 19:27:36 +02:00
a8ccb6990b fix last refs to bootstrap-pods 2023-05-15 16:54:29 +02:00
b1cdb30622 boostrap pods -> static pods 2023-05-15 16:22:04 +02:00
50bb60823f yaml render func 2023-05-15 15:54:23 +02:00
482d3c83ba host_by_group: scope in cluster 2023-05-15 15:54:16 +02:00
74abbf9eda cluster: addons as list 2023-05-15 14:47:53 +02:00
51 changed files with 1391 additions and 818 deletions

View File

@ -1 +1,3 @@
tmp tmp
dist
test-run

View File

@ -1,23 +1,33 @@
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
from mcluseau/golang-builder:1.20.3 as build from golang:1.24.3-bookworm as build
run apt-get update && apt-get install -y git
workdir /src
copy go.mod go.sum ./
run \
--mount=type=cache,id=gomod,target=/go/pkg/mod \
--mount=type=cache,id=gobuild,target=/root/.cache/go-build \
go mod download
arg GIT_TAG
copy . ./
run \
--mount=type=cache,id=gomod,target=/go/pkg/mod \
--mount=type=cache,id=gobuild,target=/root/.cache/go-build \
go test ./... && \
hack/build ./...
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
from debian:stretch from debian:bookworm
entrypoint ["/bin/dkl-local-server"] entrypoint ["/bin/dkl-local-server"]
env _uncache 1 env _uncache=1
run apt-get update \ run apt-get update \
&& apt-get install -y genisoimage gdisk dosfstools util-linux udev binutils systemd \ && yes |apt-get install -y genisoimage gdisk dosfstools util-linux udev binutils systemd \
grub2 grub-pc-bin grub-efi-amd64-bin ca-certificates curl openssh-client qemu-utils \
&& apt-get clean && apt-get clean
run yes |apt-get install -y grub2 grub-pc-bin grub-efi-amd64-bin \ copy --from=build /src/dist/ /bin/
&& apt-get clean
run apt-get install -y ca-certificates curl openssh-client \
&& apt-get clean
run curl -L https://github.com/vmware/govmomi/releases/download/v0.21.0/govc_linux_amd64.gz | gunzip > /bin/govc && chmod +x /bin/govc
copy upload-vmware.sh govc.env /var/lib/direktil/
copy --from=build /go/bin/ /bin/

View File

@ -6,6 +6,7 @@ import (
"io" "io"
"log" "log"
"os" "os"
"os/exec"
"strings" "strings"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -116,9 +117,26 @@ func eachFragment(path string, searchList []FS, walk func(io.Reader) error) (err
continue continue
} }
genCmd, found := strings.CutPrefix(line, "#!gen ")
if found {
cmdArgs := strings.Fields(genCmd)
if Debug {
log.Print("#!gen ", cmdArgs)
}
cmd := "gen/" + cmdArgs[0]
args := cmdArgs[1:]
genOutput, err := exec.Command(cmd, args...).Output()
if err != nil {
return fmt.Errorf("gen %v: %w", cmdArgs, err)
}
walk(bytes.NewBuffer(genOutput))
continue
}
includePath, found := strings.CutPrefix(line, "#!include ") includePath, found := strings.CutPrefix(line, "#!include ")
if !found { if !found {
continue // or break? continue
} }
includePath = strings.TrimSpace(includePath) includePath = strings.TrimSpace(includePath)
@ -127,8 +145,7 @@ func eachFragment(path string, searchList []FS, walk func(io.Reader) error) (err
} }
err = eachFragment(includePath, searchList, walk) err = eachFragment(includePath, searchList, walk)
if err != nil { if err != nil {
err = fmt.Errorf("include %q: %w", includePath, err) return fmt.Errorf("include %q: %w", includePath, err)
return
} }
} }

View File

@ -16,6 +16,8 @@ import (
"novit.tech/direktil/local-server/pkg/clustersconfig" "novit.tech/direktil/local-server/pkg/clustersconfig"
) )
var Version = "dev"
var ( var (
Debug = false Debug = false
@ -50,11 +52,6 @@ func main() {
openIncludes() openIncludes()
if false {
assemble("hosts/m1")
log.Fatal("--- debug: end ---")
}
loadSrc() loadSrc()
dst = &localconfig.Config{ dst = &localconfig.Config{
@ -94,7 +91,7 @@ func main() {
ctx.Host.Versions["modules"] = ctx.Host.Kernel ctx.Host.Versions["modules"] = ctx.Host.Kernel
} }
dst.Hosts = append(dst.Hosts, &localconfig.Host{ renderedHost := &localconfig.Host{
Name: host.Name, Name: host.Name,
ClusterName: ctx.Cluster.Name, ClusterName: ctx.Cluster.Name,
@ -113,7 +110,13 @@ func main() {
BootstrapConfig: ctx.BootstrapConfig(), BootstrapConfig: ctx.BootstrapConfig(),
Config: ctx.Config(), Config: ctx.Config(),
}) }
if host.Template {
dst.HostTemplates = append(dst.HostTemplates, renderedHost)
} else {
dst.Hosts = append(dst.Hosts, renderedHost)
}
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@ -124,6 +127,8 @@ func main() {
defer out.Close() defer out.Close()
out.Write([]byte("# dkl-dir2config " + Version + "\n"))
if err = yaml.NewEncoder(out).Encode(dst); err != nil { if err = yaml.NewEncoder(out).Encode(dst); err != nil {
log.Fatal("failed to render output: ", err) log.Fatal("failed to render output: ", err)
} }

View File

@ -9,12 +9,12 @@ import (
"novit.tech/direktil/local-server/pkg/clustersconfig" "novit.tech/direktil/local-server/pkg/clustersconfig"
) )
func clusterFuncs(clusterSpec *clustersconfig.Cluster) map[string]interface{} { func clusterFuncs(clusterSpec *clustersconfig.Cluster) map[string]any {
cluster := clusterSpec.Name cluster := clusterSpec.Name
return map[string]interface{}{ return map[string]any{
"password": func(name string) (s string) { "password": func(name, hash string) (s string) {
return fmt.Sprintf("{{ password %q %q }}", cluster, name) return fmt.Sprintf("{{ password %q %q %q | quote }}", cluster, name, hash)
}, },
"token": func(name string) (s string) { "token": func(name string) (s string) {
@ -36,7 +36,7 @@ func clusterFuncs(clusterSpec *clustersconfig.Cluster) map[string]interface{} {
return fmt.Sprintf("{{ ca_dir %q %q }}", cluster, name), nil return fmt.Sprintf("{{ ca_dir %q %q }}", cluster, name), nil
}, },
"hosts_by_cluster": func(cluster string) (hosts []interface{}) { "hosts_by_cluster": func(cluster string) (hosts []any) {
for _, host := range src.Hosts { for _, host := range src.Hosts {
if host.Cluster == cluster { if host.Cluster == cluster {
hosts = append(hosts, asMap(host)) hosts = append(hosts, asMap(host))
@ -50,9 +50,9 @@ func clusterFuncs(clusterSpec *clustersconfig.Cluster) map[string]interface{} {
return return
}, },
"hosts_by_group": func(group string) (hosts []interface{}) { "hosts_by_group": func(group string) (hosts []any) {
for _, host := range src.Hosts { for _, host := range src.Hosts {
if host.Group == group { if host.Cluster == cluster && host.Group == group {
hosts = append(hosts, asMap(host)) hosts = append(hosts, asMap(host))
} }
} }
@ -63,6 +63,26 @@ func clusterFuncs(clusterSpec *clustersconfig.Cluster) map[string]interface{} {
return return
}, },
"host_ip_from": func(hostName, net string) string {
host := src.Host(hostName)
if host == nil {
log.Printf("WARNING: no host named %q", hostName)
return "<no value>"
}
ipFrom := host.IPFrom
if ipFrom == nil {
ipFrom = map[string]string{}
}
ip, ok := ipFrom[net]
if !ok {
ip = host.IP
}
return ip
},
} }
} }
@ -101,12 +121,18 @@ func renderAddons(cluster *clustersconfig.Cluster) string {
return "" return ""
} }
addons := src.Addons[cluster.Addons] buf := new(bytes.Buffer)
for _, addonSet := range cluster.Addons {
addons := src.Addons[addonSet]
if addons == nil { if addons == nil {
log.Fatalf("cluster %q: no addons with name %q", cluster.Name, cluster.Addons) log.Fatalf("cluster %q: no addons with name %q", cluster.Name, addonSet)
} }
return string(renderClusterTemplates(cluster, "addons", addons)) buf.Write(renderClusterTemplates(cluster, "addons", addons))
}
return buf.String()
} }
type namePod struct { type namePod struct {

View File

@ -2,15 +2,15 @@ package main
import ( import (
"bytes" "bytes"
"crypto/sha1"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"log" "log"
"math/rand"
"path" "path"
"reflect" "reflect"
"strings" "strings"
"github.com/cespare/xxhash"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"novit.tech/direktil/pkg/config" "novit.tech/direktil/pkg/config"
@ -156,8 +156,8 @@ func (ctx *renderContext) renderConfigTo(buf io.Writer, configTemplate *clusters
extraFuncs := ctx.templateFuncs(ctxMap) extraFuncs := ctx.templateFuncs(ctxMap)
extraFuncs["bootstrap_pods_files"] = func(dir string) (string, error) { extraFuncs["static_pods_files"] = func(dir string) (string, error) {
namePods := ctx.renderBootstrapPods() namePods := ctx.renderStaticPods()
defs := make([]config.FileDef, 0) defs := make([]config.FileDef, 0)
@ -166,7 +166,7 @@ func (ctx *renderContext) renderConfigTo(buf io.Writer, configTemplate *clusters
ba, err := yaml.Marshal(namePod.Pod) ba, err := yaml.Marshal(namePod.Pod)
if err != nil { if err != nil {
return "", fmt.Errorf("bootstrap pod %s: failed to render: %v", name, err) return "", fmt.Errorf("static pod %s: failed to render: %v", name, err)
} }
defs = append(defs, config.FileDef{ defs = append(defs, config.FileDef{
@ -180,17 +180,30 @@ func (ctx *renderContext) renderConfigTo(buf io.Writer, configTemplate *clusters
return string(ba), err return string(ba), err
} }
extraFuncs["machine_id"] = func() string { extraFuncs["host_ip"] = func() string {
ba := sha1.Sum([]byte(ctx.Cluster.Name + "/" + ctx.Host.Name)) // TODO: check semantics of machine-id if ctx.Host.Template {
return hex.EncodeToString(ba[:]) return "{{ host_ip }}"
} }
return ctx.Host.IP
}
extraFuncs["host_name"] = func() string {
if ctx.Host.Template {
return "{{ host_name }}"
}
return ctx.Host.Name
}
extraFuncs["machine_id"] = func() string {
return "{{ machine_id }}"
}
extraFuncs["version"] = func() string { return Version }
if err := configTemplate.Execute(ctxName, "config", buf, ctxMap, extraFuncs); err != nil { if err := configTemplate.Execute(ctxName, "config", buf, ctxMap, extraFuncs); err != nil {
log.Fatalf("failed to render config %q for host %q: %v", ctx.Host.Config, ctx.Host.Name, err) log.Fatalf("failed to render config %q for host %q: %v", ctx.Host.Config, ctx.Host.Name, err)
} }
} }
func (ctx *renderContext) templateFuncs(ctxMap map[string]interface{}) map[string]interface{} { func (ctx *renderContext) templateFuncs(ctxMap map[string]any) map[string]interface{} {
cluster := ctx.Cluster.Name cluster := ctx.Cluster.Name
getKeyCert := func(name, funcName string) (s string, err error) { getKeyCert := func(name, funcName string) (s string, err error) {
@ -231,7 +244,25 @@ func (ctx *renderContext) templateFuncs(ctxMap map[string]interface{}) map[strin
} }
funcs := clusterFuncs(ctx.Cluster) funcs := clusterFuncs(ctx.Cluster)
for k, v := range map[string]interface{}{ for k, v := range map[string]any{
"default": func(value, defaultValue any) any {
switch v := value.(type) {
case string:
if v != "" {
return v
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, float32, float64:
if v != 0 {
return v
}
default:
if v != nil {
return v
}
}
return defaultValue
},
"tls_key": func(name string) (string, error) { "tls_key": func(name string) (string, error) {
return getKeyCert(name, "tls_key") return getKeyCert(name, "tls_key")
}, },
@ -252,11 +283,11 @@ func (ctx *renderContext) templateFuncs(ctxMap map[string]interface{}) map[strin
return "{{ host_download_token }}" return "{{ host_download_token }}"
}, },
"hosts_of_group": func() (hosts []interface{}) { "hosts_of_group": func() (hosts []any) {
hosts = make([]interface{}, 0) hosts = make([]any, 0)
for _, host := range ctx.clusterConfig.Hosts { for _, host := range ctx.clusterConfig.Hosts {
if host.Group != ctx.Host.Group { if host.Cluster == ctx.Cluster.Name && host.Group != ctx.Host.Group {
continue continue
} }
@ -268,12 +299,31 @@ func (ctx *renderContext) templateFuncs(ctxMap map[string]interface{}) map[strin
"hosts_of_group_count": func() (count int) { "hosts_of_group_count": func() (count int) {
for _, host := range ctx.clusterConfig.Hosts { for _, host := range ctx.clusterConfig.Hosts {
if host.Group == ctx.Host.Group { if host.Cluster == ctx.Cluster.Name && host.Group == ctx.Host.Group {
count++ count++
} }
} }
return return
}, },
"shuffled_hosts_by_group": func(group string) (hosts []any) {
for _, host := range src.Hosts {
if host.Cluster == ctx.Cluster.Name && host.Group == group {
hosts = append(hosts, asMap(host))
}
}
if len(hosts) == 0 {
log.Printf("WARNING: no hosts in group %q", group)
return
}
seed := xxhash.Sum64String(ctx.Host.Name)
rng := rand.New(rand.NewSource(int64(seed)))
rng.Shuffle(len(hosts), func(i, j int) { hosts[i], hosts[j] = hosts[j], hosts[i] })
return
},
} { } {
funcs[k] = v funcs[k] = v
} }

View File

@ -10,18 +10,18 @@ import (
"novit.tech/direktil/local-server/pkg/clustersconfig" "novit.tech/direktil/local-server/pkg/clustersconfig"
) )
func (ctx *renderContext) renderBootstrapPods() (pods []namePod) { func (ctx *renderContext) renderStaticPods() (pods []namePod) {
if ctx.Host.BootstrapPods == "" { if ctx.Host.StaticPods == "" {
return return
} }
bootstrapPods, ok := src.BootstrapPods[ctx.Host.BootstrapPods] staticPods, ok := src.StaticPods[ctx.Host.StaticPods]
if !ok { if !ok {
log.Fatalf("no bootstrap pods template named %q", ctx.Host.BootstrapPods) log.Fatalf("no static pods template named %q", ctx.Host.StaticPods)
} }
// render bootstrap pods // render static pods
parts := bytes.Split(ctx.renderHostTemplates("bootstrap-pods", bootstrapPods), []byte("\n---\n")) parts := bytes.Split(ctx.renderHostTemplates("static-pods", staticPods), []byte("\n---\n"))
for _, part := range parts { for _, part := range parts {
buf := bytes.NewBuffer(part) buf := bytes.NewBuffer(part)
dec := yaml.NewDecoder(buf) dec := yaml.NewDecoder(buf)
@ -35,7 +35,7 @@ func (ctx *renderContext) renderBootstrapPods() (pods []namePod) {
if err == io.EOF { if err == io.EOF {
break break
} else if err != nil { } else if err != nil {
log.Fatalf("bootstrap pod %d: failed to parse: %v\n%s", n, err, str) log.Fatalf("static pod %d: failed to parse: %v\n%s", n, err, str)
} }
if len(podMap) == 0 { if len(podMap) == 0 {
@ -43,7 +43,7 @@ func (ctx *renderContext) renderBootstrapPods() (pods []namePod) {
} }
if podMap["metadata"] == nil { if podMap["metadata"] == nil {
log.Fatalf("bootstrap pod %d: no metadata\n%s", n, buf.String()) log.Fatalf("static pod %d: no metadata\n%s", n, buf.String())
} }
md := podMap["metadata"].(map[interface{}]interface{}) md := podMap["metadata"].(map[interface{}]interface{})
@ -63,7 +63,7 @@ func (ctx *renderContext) renderHostTemplates(setName string,
log.Print("rendering host templates in ", setName) log.Print("rendering host templates in ", setName)
buf := &bytes.Buffer{} buf := bytes.NewBuffer(make([]byte, 0, 16<<10))
for _, t := range templates { for _, t := range templates {
log.Print("- template: ", setName, ": ", t.Name) log.Print("- template: ", setName, ": ", t.Name)

View File

@ -3,7 +3,6 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"sort" "sort"
) )
@ -23,7 +22,7 @@ func read(path string) (ba []byte, err error) {
} }
defer r.Close() defer r.Close()
return ioutil.ReadAll(r) return io.ReadAll(r)
} }
err = fmt.Errorf("%s: %w", path, os.ErrNotExist) err = fmt.Errorf("%s: %w", path, os.ErrNotExist)

View File

@ -1,28 +1,19 @@
package main package main
import ( import (
"flag"
"log" "log"
"net/http" "net/http"
) )
var ( var adminToken string
hostsToken = flag.String("hosts-token", "", "Token to give to access /hosts (open is none)")
adminToken = flag.String("admin-token", "", "Token to give to access to admin actions (open is none)")
)
func authorizeHosts(r *http.Request) bool {
return authorizeToken(r, *hostsToken)
}
func authorizeAdmin(r *http.Request) bool { func authorizeAdmin(r *http.Request) bool {
return authorizeToken(r, *adminToken) return authorizeToken(r, adminToken)
} }
func authorizeToken(r *http.Request, token string) bool { func authorizeToken(r *http.Request, token string) bool {
if token == "" { if token == "" {
// access is open return false
return true
} }
reqToken := r.Header.Get("Authorization") reqToken := r.Header.Get("Authorization")
@ -34,13 +25,13 @@ func authorizeToken(r *http.Request, token string) bool {
} }
func forbidden(w http.ResponseWriter, r *http.Request) { func forbidden(w http.ResponseWriter, r *http.Request) {
log.Printf("denied access to %s from %s", r.RequestURI, r.RemoteAddr) log.Printf("denied access to %s from %s", r.URL.Path, r.RemoteAddr)
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
} }
func requireToken(token string, handler http.Handler) http.Handler { func requireToken(token *string, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if !authorizeToken(req, token) { if !authorizeToken(req, *token) {
forbidden(w, req) forbidden(w, req)
return return
} }
@ -49,9 +40,5 @@ func requireToken(token string, handler http.Handler) http.Handler {
} }
func requireAdmin(handler http.Handler) http.Handler { func requireAdmin(handler http.Handler) http.Handler {
return requireToken(*adminToken, handler) return requireToken(&adminToken, handler)
}
func requireHosts(handler http.Handler) http.Handler {
return requireToken(*hostsToken, handler)
} }

View File

@ -6,7 +6,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@ -18,7 +17,7 @@ import (
) )
func buildBootImg(out io.Writer, ctx *renderContext) (err error) { func buildBootImg(out io.Writer, ctx *renderContext) (err error) {
bootImg, err := ioutil.TempFile(os.TempDir(), "boot.img-") bootImg, err := os.CreateTemp(os.TempDir(), "boot.img-")
if err != nil { if err != nil {
return return
} }
@ -30,7 +29,7 @@ func buildBootImg(out io.Writer, ctx *renderContext) (err error) {
} }
// send the result // send the result
bootImg.Seek(0, os.SEEK_SET) bootImg.Seek(0, io.SeekStart)
io.Copy(out, bootImg) io.Copy(out, bootImg)
return return
} }
@ -57,7 +56,55 @@ func buildBootImgGZ(out io.Writer, ctx *renderContext) (err error) {
return return
} }
var grubSupportVersion = flag.String("grub-support", "1.0.1", "GRUB support version") func buildBootImgQemuConvert(out io.Writer, ctx *renderContext, format string) (err error) {
imgPath, err := func() (imgPath string, err error) {
bootImg, err := os.CreateTemp(os.TempDir(), "boot.img-")
if err != nil {
return
}
defer rmTempFile(bootImg)
err = setupBootImage(bootImg, ctx)
if err != nil {
return
}
err = bootImg.Sync()
if err != nil {
return
}
imgPath = bootImg.Name() + "." + format
err = run("qemu-img", "convert", "-f", "raw", "-O", format, bootImg.Name(), imgPath)
if err != nil {
return
}
return
}()
defer os.Remove(imgPath)
if err != nil {
return
}
// send the result
img, err := os.Open(imgPath)
if err != nil {
return
}
io.Copy(out, img)
return
}
func qemuImgBootImg(format string) func(out io.Writer, ctx *renderContext) (err error) {
return func(out io.Writer, ctx *renderContext) (err error) {
return buildBootImgQemuConvert(out, ctx, format)
}
}
var grubSupportVersion = flag.String("grub-support", "1.1.0", "GRUB support version")
func setupBootImage(bootImg *os.File, ctx *renderContext) (err error) { func setupBootImage(bootImg *os.File, ctx *renderContext) (err error) {
path, err := ctx.distFetch("grub-support", *grubSupportVersion) path, err := ctx.distFetch("grub-support", *grubSupportVersion)

View File

@ -3,7 +3,6 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@ -13,189 +12,8 @@ import (
"github.com/cespare/xxhash" "github.com/cespare/xxhash"
) )
// deprecated func buildBootISO(out io.Writer, ctx *renderContext) (err error) {
func buildBootISO(out io.Writer, ctx *renderContext) error { tempDir, err := os.MkdirTemp("/tmp", "iso-v2-")
tempDir, err := ioutil.TempDir("/tmp", "iso-")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
cp := func(src, dst string) error {
log.Printf("iso: adding %s as %s", src, dst)
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
outPath := filepath.Join(tempDir, dst)
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
return err
}
out, err := os.Create(outPath)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
err = func() error {
// grub
if err := os.MkdirAll(filepath.Join(tempDir, "grub"), 0755); err != nil {
return err
}
err = ioutil.WriteFile(filepath.Join(tempDir, "grub", "grub.cfg"), []byte(`
search --set=root --file /config.yaml
insmod all_video
set timeout=3
menuentry "Direktil" {
linux /vmlinuz direktil.boot=DEVNAME=sr0 direktil.boot.fs=iso9660 `+ctx.CmdLine+`
initrd /initrd
}
`), 0644)
if err != nil {
return err
}
coreImgPath := filepath.Join(tempDir, "grub", "core.img")
grubCfgPath := filepath.Join(tempDir, "grub", "grub.cfg")
cmd := exec.Command("grub-mkstandalone",
"--format=i386-pc",
"--output="+coreImgPath,
"--install-modules=linux normal iso9660 biosdisk memdisk search tar ls",
"--modules=linux normal iso9660 biosdisk search",
"--locales=",
"--fonts=",
"boot/grub/grub.cfg="+grubCfgPath,
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
defer os.Remove(coreImgPath)
defer os.Remove(grubCfgPath)
out, err := os.Create(filepath.Join(tempDir, "grub", "bios.img"))
if err != nil {
return err
}
defer out.Close()
b, err := ioutil.ReadFile("/usr/lib/grub/i386-pc/cdboot.img")
if err != nil {
return err
}
if _, err := out.Write(b); err != nil {
return err
}
b, err = ioutil.ReadFile(coreImgPath)
if err != nil {
return err
}
if _, err := out.Write(b); err != nil {
return err
}
return nil
}()
if err != nil {
return err
}
// config
cfgBytes, cfg, err := ctx.Config()
if err != nil {
return err
}
ioutil.WriteFile(filepath.Join(tempDir, "config.yaml"), cfgBytes, 0600)
// kernel and initrd
type distCopy struct {
Src []string
Dst string
}
copies := []distCopy{
{Src: []string{"kernels", ctx.Host.Kernel}, Dst: "vmlinuz"},
{Src: []string{"initrd", ctx.Host.Initrd}, Dst: "initrd"},
}
// layers
for _, layer := range cfg.Layers {
layerVersion := ctx.Host.Versions[layer]
if layerVersion == "" {
return fmt.Errorf("layer %q not mapped to a version", layer)
}
copies = append(copies,
distCopy{
Src: []string{"layers", layer, layerVersion},
Dst: filepath.Join("current", "layers", layer+".fs"),
})
}
for _, copy := range copies {
outPath, err := ctx.distFetch(copy.Src...)
if err != nil {
return err
}
err = cp(outPath, copy.Dst)
if err != nil {
return err
}
}
// build the ISO
mkisofs, err := exec.LookPath("genisoimage")
if err != nil {
mkisofs, err = exec.LookPath("mkisofs")
}
if err != nil {
return err
}
cmd := exec.Command(mkisofs,
"-quiet",
"-joliet",
"-joliet-long",
"-rock",
"-translation-table",
"-no-emul-boot",
"-boot-load-size", "4",
"-boot-info-table",
"-eltorito-boot", "grub/bios.img",
"-eltorito-catalog", "grub/boot.cat",
tempDir,
)
cmd.Stdout = out
cmd.Stderr = os.Stderr
return cmd.Run()
}
func buildBootISOv2(out io.Writer, ctx *renderContext) (err error) {
tempDir, err := ioutil.TempDir("/tmp", "iso-v2-")
if err != nil { if err != nil {
return return
} }
@ -251,7 +69,7 @@ func buildBootISOv2(out io.Writer, ctx *renderContext) (err error) {
f.Write([]byte("direktil marker file\n")) f.Write([]byte("direktil marker file\n"))
f.Close() f.Close()
err = ioutil.WriteFile(filepath.Join(tempDir, "grub", "grub.cfg"), []byte(` err = os.WriteFile(filepath.Join(tempDir, "grub", "grub.cfg"), []byte(`
search --set=root --file /`+tag+` search --set=root --file /`+tag+`
insmod all_video insmod all_video
@ -294,7 +112,7 @@ menuentry "Direktil" {
defer out.Close() defer out.Close()
b, err := ioutil.ReadFile("/usr/lib/grub/i386-pc/cdboot.img") b, err := os.ReadFile("/usr/lib/grub/i386-pc/cdboot.img")
if err != nil { if err != nil {
return err return err
} }
@ -303,7 +121,7 @@ menuentry "Direktil" {
return err return err
} }
b, err = ioutil.ReadFile(coreImgPath) b, err = os.ReadFile(coreImgPath)
if err != nil { if err != nil {
return err return err
} }
@ -320,7 +138,7 @@ menuentry "Direktil" {
// kernel and initrd // kernel and initrd
buildRes(fetchKernel, "vmlinuz") buildRes(fetchKernel, "vmlinuz")
buildRes(buildInitrdV2, "initrd") buildRes(buildInitrd, "initrd")
// build the ISO // build the ISO
mkisofs, err := exec.LookPath("genisoimage") mkisofs, err := exec.LookPath("genisoimage")

View File

@ -4,7 +4,6 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
@ -37,7 +36,7 @@ func buildBootTar(out io.Writer, ctx *renderContext) (err error) {
return return
} }
kernelBytes, err := ioutil.ReadFile(kernelPath) kernelBytes, err := os.ReadFile(kernelPath)
if err != nil { if err != nil {
return return
} }
@ -49,7 +48,7 @@ func buildBootTar(out io.Writer, ctx *renderContext) (err error) {
// initrd // initrd
initrd := new(bytes.Buffer) initrd := new(bytes.Buffer)
err = buildInitrdV2(initrd, ctx) err = buildInitrd(initrd, ctx)
if err != nil { if err != nil {
return return
} }
@ -98,7 +97,7 @@ func buildBootEFITar(out io.Writer, ctx *renderContext) (err error) {
return return
} }
kernelBytes, err := ioutil.ReadFile(kernelPath) kernelBytes, err := os.ReadFile(kernelPath)
if err != nil { if err != nil {
return return
} }
@ -110,7 +109,7 @@ func buildBootEFITar(out io.Writer, ctx *renderContext) (err error) {
// initrd // initrd
initrd := new(bytes.Buffer) initrd := new(bytes.Buffer)
err = buildInitrdV2(initrd, ctx) err = buildInitrd(initrd, ctx)
if err != nil { if err != nil {
return return
} }

View File

@ -3,7 +3,6 @@ package main
import ( import (
"archive/tar" "archive/tar"
"encoding/json" "encoding/json"
"flag"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -15,8 +14,6 @@ import (
"novit.tech/direktil/pkg/cpiocat" "novit.tech/direktil/pkg/cpiocat"
) )
var initrdV2 = flag.String("initrd-v2", "2.1.0", "initrd V2 version (temporary flag)") // FIXME
func renderBootstrapConfig(w http.ResponseWriter, r *http.Request, ctx *renderContext, asJson bool) (err error) { func renderBootstrapConfig(w http.ResponseWriter, r *http.Request, ctx *renderContext, asJson bool) (err error) {
log.Printf("sending bootstrap config for %q", ctx.Host.Name) log.Printf("sending bootstrap config for %q", ctx.Host.Name)
@ -34,7 +31,7 @@ func renderBootstrapConfig(w http.ResponseWriter, r *http.Request, ctx *renderCo
return nil return nil
} }
func buildInitrdV2(out io.Writer, ctx *renderContext) (err error) { func buildInitrd(out io.Writer, ctx *renderContext) (err error) {
_, cfg, err := ctx.Config() _, cfg, err := ctx.Config()
if err != nil { if err != nil {
return return
@ -43,7 +40,7 @@ func buildInitrdV2(out io.Writer, ctx *renderContext) (err error) {
cat := cpiocat.New(out) cat := cpiocat.New(out)
// initrd // initrd
initrdPath, err := ctx.distFetch("initrd", *initrdV2) initrdPath, err := ctx.distFetch("initrd", ctx.Host.Initrd)
if err != nil { if err != nil {
return return
} }
@ -73,7 +70,9 @@ func buildInitrdV2(out io.Writer, ctx *renderContext) (err error) {
// ssh keys // ssh keys
// FIXME we want a bootstrap-stage key instead of the real host key // FIXME we want a bootstrap-stage key instead of the real host key
cat.AppendBytes(cfg.FileContent("/etc/ssh/ssh_host_rsa_key"), "id_rsa", 0600) for _, format := range []string{"rsa", "dsa", "ecdsa", "ed25519"} {
cat.AppendBytes(cfg.FileContent("/etc/ssh/ssh_host_"+format+"_key"), "id_"+format, 0600)
}
return cat.Close() return cat.Close()
} }

View File

@ -1,15 +1,20 @@
package main package main
import ( import (
"crypto/rand"
"encoding/base32"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"path" "path"
"strconv"
"strings"
cfsslconfig "github.com/cloudflare/cfssl/config" cfsslconfig "github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr" "github.com/cloudflare/cfssl/csr"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"novit.tech/direktil/pkg/bootstrapconfig"
"novit.tech/direktil/pkg/config" "novit.tech/direktil/pkg/config"
) )
@ -28,16 +33,50 @@ func templateFuncs(sslCfg *cfsslconfig.Config) map[string]any {
return getUsableKeyCert(cluster, caName, name, profile, label, certReq, sslCfg) 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{ return map[string]any{
"password": func(cluster, name string) (password string, err error) { "quote": strconv.Quote,
password, _, err = clusterPasswords.Get(cluster + "/" + name)
"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 { if err != nil {
return return
} }
if len(password) == 0 {
err = fmt.Errorf("password %q not defined for cluster %q", name, cluster) return hash([]byte(password), seed, hashAlg)
}
return
}, },
"token": getOrCreateClusterToken, "token": getOrCreateClusterToken,

View File

@ -10,5 +10,7 @@ var (
ErrNotFound = httperr.NotFound ErrNotFound = httperr.NotFound
ErrUnauthorized = httperr.StdStatus(http.StatusUnauthorized) ErrUnauthorized = httperr.StdStatus(http.StatusUnauthorized)
ErrForbidden = httperr.StdStatus(http.StatusForbidden) ErrForbidden = httperr.StdStatus(http.StatusForbidden)
ErrInternal = httperr.StdStatus(http.StatusInternalServerError)
ErrInvalidToken = httperr.NewStd(1000, http.StatusForbidden, "invalid token") ErrInvalidToken = httperr.NewStd(1000, http.StatusForbidden, "invalid token")
ErrStoreLocked = httperr.NewStd(1001, http.StatusServiceUnavailable, "store is locked")
) )

View File

@ -2,13 +2,9 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io"
"log" "log"
"net/http" "net/http"
"os"
cpio "github.com/cavaliergopher/cpio"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
@ -28,84 +24,3 @@ func renderConfig(w http.ResponseWriter, r *http.Request, ctx *renderContext, as
return nil return nil
} }
func buildInitrd(out io.Writer, ctx *renderContext) error {
_, cfg, err := ctx.Config()
if err != nil {
return err
}
// send initrd basis
initrdPath, err := ctx.distFetch("initrd", ctx.Host.Initrd)
if err != nil {
return err
}
err = writeFile(out, initrdPath)
if err != nil {
return err
}
// and our extra archive
archive := cpio.NewWriter(out)
// - required dirs
for _, dir := range []string{
"boot",
"boot/current",
"boot/current/layers",
} {
archive.WriteHeader(&cpio.Header{
Name: dir,
Mode: cpio.FileMode(0600 | os.ModeDir),
})
}
// - the layers
for _, layer := range cfg.Layers {
layerVersion := ctx.Host.Versions[layer]
if layerVersion == "" {
return fmt.Errorf("layer %q not mapped to a version", layer)
}
path, err := ctx.distFetch("layers", layer, layerVersion)
if err != nil {
return err
}
stat, err := os.Stat(path)
if err != nil {
return err
}
archive.WriteHeader(&cpio.Header{
Name: "boot/current/layers/" + layer + ".fs",
Mode: 0600,
Size: stat.Size(),
})
if err = writeFile(archive, path); err != nil {
return err
}
}
// - the configuration
ba, err := yaml.Marshal(cfg)
if err != nil {
return err
}
archive.WriteHeader(&cpio.Header{
Name: "boot/config.yaml",
Mode: 0600,
Size: int64(len(ba)),
})
archive.Write(ba)
// finalize the archive
archive.Flush()
archive.Close()
return nil
}

View File

@ -21,13 +21,15 @@ const (
etcDir = "/etc/direktil" etcDir = "/etc/direktil"
) )
var Version = "dev"
var ( var (
address = flag.String("address", ":7606", "HTTP listen address") address = flag.String("address", ":7606", "HTTP listen address")
tlsAddress = flag.String("tls-address", "", "HTTPS listen address") tlsAddress = flag.String("tls-address", "", "HTTPS listen address")
certFile = flag.String("tls-cert", etcDir+"/server.crt", "Server TLS certificate") certFile = flag.String("tls-cert", etcDir+"/server.crt", "Server TLS certificate")
keyFile = flag.String("tls-key", etcDir+"/server.key", "Server TLS key") keyFile = flag.String("tls-key", etcDir+"/server.key", "Server TLS key")
autoUnlock = flag.String("auto-unlock", "", "Auto-unlock store (testing only!)") autoUnlock = flag.String("auto-unlock", "", "Auto-unlock store (testing only!) env: DLS_AUTO_UNLOCK")
casStore cas.Store casStore cas.Store
) )
@ -41,6 +43,9 @@ func main() {
log.Fatal("no listen address given") log.Fatal("no listen address given")
} }
log.Print("Direktil local-server version ", Version)
wPublicState.Change(func(s *PublicState) { s.ServerVersion = Version })
computeUIHash() computeUIHash()
openSecretStore() openSecretStore()
@ -52,12 +57,12 @@ func main() {
} }
if autoUnlock != "" { if autoUnlock != "" {
log.Printf("auto-unlocking the store") log.Printf("auto-unlocking the store")
err := unlockSecretStore([]byte(autoUnlock)) err := unlockSecretStore("test", []byte(autoUnlock))
if err.Any() { if err.Any() {
log.Fatal(err) log.Fatal(err)
} }
log.Print("store auto-unlocked, admin token is ", *adminToken) log.Print("store auto-unlocked")
} }
os.Setenv("DLS_AUTO_UNLOCK", "") os.Setenv("DLS_AUTO_UNLOCK", "")

View File

@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"crypto/sha1"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@ -175,6 +176,17 @@ func (ctx *renderContext) TemplateFuncs() map[string]any {
funcs := templateFuncs(ctx.SSLConfig) funcs := templateFuncs(ctx.SSLConfig)
for name, method := range map[string]any{ for name, method := range map[string]any{
"host_ip": func() (s string) {
return ctx.Host.IPs[0]
},
"host_name": func() (s string) {
return ctx.Host.Name
},
"machine_id": func() (s string) {
ba := sha1.Sum([]byte(ctx.Host.ClusterName + "/" + ctx.Host.Name))
return hex.EncodeToString(ba[:])
},
"ssh_host_keys": func(dir, cluster, host string) (s string, err error) { "ssh_host_keys": func(dir, cluster, host string) (s string, err error) {
if host == "" { if host == "" {
host = ctx.Host.Name host = ctx.Host.Name

View File

@ -59,11 +59,11 @@ func openSecretStore() {
var ( var (
unlockMutex = sync.Mutex{} unlockMutex = sync.Mutex{}
ErrStoreAlreadyUnlocked = httperr.NewStd(http.StatusConflict, 1, "store already unlocked") ErrStoreAlreadyUnlocked = httperr.NewStd(1, http.StatusConflict, "store already unlocked")
ErrInvalidPassphrase = httperr.NewStd(http.StatusBadRequest, 2, "invalid passphrase") ErrInvalidPassphrase = httperr.NewStd(2, http.StatusBadRequest, "invalid passphrase")
) )
func unlockSecretStore(passphrase []byte) (err httperr.Error) { func unlockSecretStore(name string, passphrase []byte) (err httperr.Error) {
unlockMutex.Lock() unlockMutex.Lock()
defer unlockMutex.Unlock() defer unlockMutex.Unlock()
@ -72,7 +72,7 @@ func unlockSecretStore(passphrase []byte) (err httperr.Error) {
} }
if secStore.IsNew() { if secStore.IsNew() {
err := secStore.Init(passphrase) err := secStore.Init(name, passphrase)
if err != nil { if err != nil {
return httperr.Internal(err) return httperr.Internal(err)
} }
@ -117,7 +117,7 @@ func unlockSecretStore(passphrase []byte) (err httperr.Error) {
log.Print("wrote new admin token") log.Print("wrote new admin token")
} }
*adminToken = token adminToken = token
{ {
token, err := newToken(16) token, err := newToken(16)
@ -247,6 +247,31 @@ func (s KVSecrets[T]) Data() (kvs map[string]T, err error) {
return return
} }
type KV[T any] struct {
K string
V T
}
func (s KVSecrets[T]) List(prefix string) (list []KV[T], err error) {
data, err := s.Data()
if err != nil {
return
}
list = make([]KV[T], 0, len(data))
for k, v := range data {
if !strings.HasPrefix(k, prefix) {
continue
}
list = append(list, KV[T]{k, v})
}
sort.Slice(list, func(i, j int) bool { return list[i].K < list[j].K })
return
}
func (s KVSecrets[T]) Keys(prefix string) (keys []string, err error) { func (s KVSecrets[T]) Keys(prefix string) (keys []string, err error) {
kvs, err := s.Data() kvs, err := s.Data()
if err != nil { if err != nil {
@ -291,6 +316,38 @@ func (s KVSecrets[T]) Put(key string, v T) (err error) {
return return
} }
func (s KVSecrets[T]) Del(key string) (err error) {
secL.Lock()
defer secL.Unlock()
kvs, err := s.Data()
if err != nil {
return
}
delete(kvs, key)
err = writeSecret(s.Name, kvs)
return
}
func (s KVSecrets[T]) GetOrCreate(key string, create func() (T, error)) (v T, err error) {
v, found, err := s.Get(key)
if err != nil {
return
}
if !found {
v, err = create()
if err != nil {
return
}
err = s.Put(key, v)
}
return
}
func (s KVSecrets[T]) WsList(resp *restful.Response, prefix string) { func (s KVSecrets[T]) WsList(resp *restful.Response, prefix string) {
keys, err := s.Keys(prefix) keys, err := s.Keys(prefix)
if err != nil { if err != nil {
@ -330,3 +387,11 @@ func (s KVSecrets[T]) WsPut(req *restful.Request, resp *restful.Response, key st
return return
} }
} }
func (s KVSecrets[T]) WsDel(req *restful.Request, resp *restful.Response, key string) {
err := s.Del(key)
if err != nil {
wsError(resp, err)
return
}
}

View File

@ -3,7 +3,6 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@ -43,7 +42,7 @@ func loadSecretData(config *config.Config) (sd *SecretData, err error) {
config: config, config: config,
} }
ba, err := ioutil.ReadFile(secretDataPath()) ba, err := os.ReadFile(secretDataPath())
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = nil err = nil

View File

@ -0,0 +1,39 @@
package main
import (
"encoding/base64"
crypthash "github.com/sergeymakinen/go-crypt/hash"
"github.com/sergeymakinen/go-crypt/sha512"
)
// for some reason, no implementation of crypt's sha512 is clean enough :(
func sha512crypt(password, seed []byte) (string, error) {
// loose salt entropy because of character restriction in the salt
salt := []byte(base64.RawStdEncoding.EncodeToString(seed))[:sha512.MaxSaltLength]
// - base64 allows '+' where the salt accepts '.'
for i, c := range salt {
if c == '+' {
salt[i] = '.'
}
}
scheme := struct {
HashPrefix string
Rounds uint32 `hash:"param:rounds,omitempty"`
Salt []byte
Sum [86]byte
}{
HashPrefix: sha512.Prefix,
Rounds: sha512.DefaultRounds,
Salt: salt,
}
key, err := sha512.Key([]byte(password), scheme.Salt, scheme.Rounds)
if err != nil {
return "", err
}
crypthash.LittleEndianEncoding.Encode(scheme.Sum[:], key)
return crypthash.Marshal(scheme)
}

View File

@ -10,7 +10,6 @@ import (
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
) )
@ -42,7 +41,7 @@ genLoop:
} }
err = func() (err error) { err = func() (err error) {
outFile, err := ioutil.TempFile("/tmp", "dls-key.") outFile, err := os.CreateTemp("/tmp", "dls-key.")
if err != nil { if err != nil {
return return
} }
@ -70,12 +69,12 @@ genLoop:
return return
} }
privKey, err = ioutil.ReadFile(outPath) privKey, err = os.ReadFile(outPath)
if err != nil { if err != nil {
return return
} }
pubKey, err = ioutil.ReadFile(outPath + ".pub") pubKey, err = os.ReadFile(outPath + ".pub")
if err != nil { if err != nil {
return return
} }

View File

@ -8,6 +8,7 @@ import (
) )
type PublicState struct { type PublicState struct {
ServerVersion string
UIHash string UIHash string
Store struct { Store struct {
New bool New bool
@ -22,6 +23,7 @@ type State struct {
Store struct { Store struct {
DownloadToken string DownloadToken string
KeyNames []string
} }
Clusters []ClusterState Clusters []ClusterState
@ -29,6 +31,8 @@ type State struct {
Config *localconfig.Config Config *localconfig.Config
Downloads map[string]DownloadSpec Downloads map[string]DownloadSpec
HostTemplates []string
} }
type ClusterState struct { type ClusterState struct {
@ -43,6 +47,8 @@ type HostState struct {
Name string Name string
Cluster string Cluster string
IPs []string IPs []string
Template string `json:",omitempty"`
} }
type CAState struct { type CAState struct {
@ -59,14 +65,21 @@ func init() {
func updateState() { func updateState() {
log.Print("updating state") log.Print("updating state")
// store key names
keyNames := make([]string, 0, len(secStore.Keys))
for _, key := range secStore.Keys {
keyNames = append(keyNames, key.Name)
}
// config
cfg, err := readConfig() cfg, err := readConfig()
if err != nil { if err != nil {
wState.Change(func(v *State) { v.HasConfig = false; v.Config = nil }) wState.Change(func(v *State) { v.HasConfig = false; v.Config = nil; v.Store.KeyNames = keyNames })
return return
} }
if secStore.IsNew() || !secStore.Unlocked() { if secStore.IsNew() || !secStore.Unlocked() {
wState.Change(func(v *State) { v.HasConfig = false; v.Config = nil }) wState.Change(func(v *State) { v.HasConfig = false; v.Config = nil; v.Store.KeyNames = keyNames })
return return
} }
@ -108,22 +121,44 @@ func updateState() {
clusters = append(clusters, c) clusters = append(clusters, c)
} }
hosts := make([]HostState, 0, len(cfg.Hosts)) hfts, err := hostsFromTemplate.List("")
if err != nil {
log.Print("failed to read hosts from template: ", err)
}
hosts := make([]HostState, 0, len(cfg.Hosts)+len(hfts))
for _, host := range cfg.Hosts { for _, host := range cfg.Hosts {
h := HostState{ h := HostState{
Name: host.Name, Name: host.Name,
Cluster: host.ClusterName, Cluster: host.ClusterName,
IPs: host.IPs, IPs: host.IPs,
} }
hosts = append(hosts, h) hosts = append(hosts, h)
} }
for _, kv := range hfts {
name, hft := kv.K, kv.V
h := HostState{
Name: name,
Cluster: hft.ClusterName(cfg),
IPs: []string{hft.IP},
Template: hft.Template,
}
hosts = append(hosts, h)
}
hostTemplates := make([]string, len(cfg.HostTemplates))
for i, ht := range cfg.HostTemplates {
hostTemplates[i] = ht.Name
}
// done // done
wState.Change(func(v *State) { wState.Change(func(v *State) {
v.HasConfig = true v.HasConfig = true
//v.Config = cfg v.Store.KeyNames = keyNames
v.Clusters = clusters v.Clusters = clusters
v.Hosts = hosts v.Hosts = hosts
v.HostTemplates = hostTemplates
}) })
} }

View File

@ -2,6 +2,8 @@ package main
import ( import (
"crypto" "crypto"
"crypto/x509"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@ -129,7 +131,26 @@ func getUsableKeyCert(cluster, caName, name, profile, label string, req *csr.Cer
if found { if found {
if rh == kc.ReqHash { if rh == kc.ReqHash {
err = func() (err error) {
err = checkCertUsable(kc.Cert) err = checkCertUsable(kc.Cert)
if err != nil {
return
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(ca.Cert) {
panic("unexpected invalid CA certificate at this point")
}
certBlock, _ := pem.Decode(kc.Cert)
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return
}
_, err = cert.Verify(x509.VerifyOptions{Roots: pool})
return
}()
if err == nil { if err == nil {
return // all good, no need to create or renew return // all good, no need to create or renew
} }

View File

@ -7,18 +7,14 @@ import (
) )
func adminAuth(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { func adminAuth(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
tokenAuth(req, resp, chain, *adminToken) tokenAuth(req, resp, chain, adminToken)
}
func hostsAuth(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
tokenAuth(req, resp, chain, *hostsToken, *adminToken)
} }
func tokenAuth(req *restful.Request, resp *restful.Response, chain *restful.FilterChain, allowedTokens ...string) { func tokenAuth(req *restful.Request, resp *restful.Response, chain *restful.FilterChain, allowedTokens ...string) {
token := getToken(req) token := getToken(req)
for _, allowedToken := range allowedTokens { for _, allowedToken := range allowedTokens {
if allowedToken == "" || token == allowedToken { if allowedToken != "" && token == allowedToken {
chain.ProcessFilter(req, resp) chain.ProcessFilter(req, resp)
return return
} }

View File

@ -2,7 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"time"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log" "github.com/cloudflare/cfssl/log"
restful "github.com/emicklei/go-restful" restful "github.com/emicklei/go-restful"
) )
@ -55,11 +57,22 @@ func getUsableClusterCA(cluster, name string) (ca CA, err error) {
if checkErr != nil { if checkErr != nil {
log.Infof("cluster %s: CA %s: regenerating certificate: %v", cluster, name, checkErr) log.Infof("cluster %s: CA %s: regenerating certificate: %v", cluster, name, checkErr)
prevCerts, _ := helpers.ParseCertificatesPEM(ca.Cert)
err = ca.RenewCert() err = ca.RenewCert()
if err != nil { if err != nil {
err = fmt.Errorf("renew: %w", err) err = fmt.Errorf("renew: %w", err)
} }
now := time.Now()
for _, cert := range prevCerts {
if cert.NotAfter.After(now) {
continue
}
certPEM := helpers.EncodeCertificatePEM(cert)
ca.Cert = append(ca.Cert, certPEM...)
}
err = clusterCAs.Put(key, ca) err = clusterCAs.Put(key, ca)
} }

View File

@ -4,6 +4,8 @@ import (
restful "github.com/emicklei/go-restful" restful "github.com/emicklei/go-restful"
) )
var seeds = newClusterSecretKV[[]byte]("seeds")
var clusterPasswords = newClusterSecretKV[string]("passwords") var clusterPasswords = newClusterSecretKV[string]("passwords")
func wsClusterPasswords(req *restful.Request, resp *restful.Response) { func wsClusterPasswords(req *restful.Request, resp *restful.Response) {

View File

@ -3,7 +3,6 @@ package main
import ( import (
"compress/gzip" "compress/gzip"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -25,7 +24,7 @@ func wsUploadConfig(req *restful.Request, resp *restful.Response) {
} }
func writeNewConfig(reader io.Reader) (err error) { func writeNewConfig(reader io.Reader) (err error) {
out, err := ioutil.TempFile(*dataDir, ".config-upload") out, err := os.CreateTemp(*dataDir, ".config-upload")
if err != nil { if err != nil {
return return
} }

View File

@ -130,7 +130,7 @@ func wsDownload(req *restful.Request, resp *restful.Response) {
} }
case "host": case "host":
host := cfg.Host(spec.Name) host := hostOrTemplate(cfg, spec.Name)
if host == nil { if host == nil {
wsNotFound(resp) wsNotFound(resp)
return return

View File

@ -13,21 +13,23 @@ import (
"novit.tech/direktil/local-server/pkg/mime" "novit.tech/direktil/local-server/pkg/mime"
) )
var trustXFF = flag.Bool("trust-xff", true, "Trust the X-Forwarded-For header") var (
allowDetectedHost = flag.Bool("allow-detected-host", false, "Allow access to host assets from its IP (insecure but enables unattended netboot)")
trustXFF = flag.Bool("trust-xff", false, "Trust the X-Forwarded-For header")
)
type wsHost struct { type wsHost struct {
prefix string
hostDoc string hostDoc string
getHost func(req *restful.Request) (hostName string, err error) getHost func(req *restful.Request) (hostName string, err error)
} }
func (ws *wsHost) register(rws *restful.WebService, alterRB func(*restful.RouteBuilder)) { func (ws wsHost) register(rws *restful.WebService, alterRB func(*restful.RouteBuilder)) {
b := func(what string) *restful.RouteBuilder { b := func(what string) *restful.RouteBuilder {
return rws.GET(ws.prefix + "/" + what).To(ws.render) return rws.GET("/" + what).To(ws.render)
} }
for _, rb := range []*restful.RouteBuilder{ for _, rb := range []*restful.RouteBuilder{
rws.GET(ws.prefix).To(ws.get). rws.GET("").To(ws.get).
Doc("Get the "+ws.hostDoc+"'s details"). Doc("Get the "+ws.hostDoc+"'s details").
Returns(200, "OK", localconfig.Host{}), Returns(200, "OK", localconfig.Host{}),
@ -44,13 +46,30 @@ func (ws *wsHost) register(rws *restful.WebService, alterRB func(*restful.RouteB
Produces(mime.DISK). Produces(mime.DISK).
Doc("Get the " + ws.hostDoc + "'s boot disk image"), Doc("Get the " + ws.hostDoc + "'s boot disk image"),
// - raw + compressed
b("boot.img.gz"). b("boot.img.gz").
Produces(mime.DISK + "+gzip"). Produces(mime.DISK + "+gzip").
Doc("Get the " + ws.hostDoc + "'s boot disk image (gzip compressed)"), Doc("Get the " + ws.hostDoc + "'s boot disk image, gzip compressed"),
b("boot.img.lz4"). b("boot.img.lz4").
Produces(mime.DISK + "+lz4"). Produces(mime.DISK + "+lz4").
Doc("Get the " + ws.hostDoc + "'s boot disk image (lz4 compressed)"), Doc("Get the " + ws.hostDoc + "'s boot disk image, lz4 compressed"),
// - other formats
b("boot.qcow2").
Produces(mime.DISK + "+qcow2").
Doc("Get the " + ws.hostDoc + "'s boot disk image, QCOW2 (KVM, Xen)"),
b("boot.qed").
Produces(mime.DISK + "+qed").
Doc("Get the " + ws.hostDoc + "'s boot disk image, QED (KVM)"),
b("boot.vmdk").
Produces(mime.DISK + "+vdi").
Doc("Get the " + ws.hostDoc + "'s boot disk image, VDI (VirtualBox)"),
b("boot.qcow2").
Produces(mime.DISK + "+vpc").
Doc("Get the " + ws.hostDoc + "'s boot disk image, VHD (Hyper-V)"),
b("boot.vmdk").
Produces(mime.DISK + "+vmdk").
Doc("Get the " + ws.hostDoc + "'s boot disk image, VMDK (VMware)"),
// metal/local HDD upgrades // metal/local HDD upgrades
b("boot.tar"). b("boot.tar").
@ -71,40 +90,31 @@ func (ws *wsHost) register(rws *restful.WebService, alterRB func(*restful.RouteB
Produces(mime.IPXE). Produces(mime.IPXE).
Doc("Get the " + ws.hostDoc + "'s IPXE code (for netboot)"), Doc("Get the " + ws.hostDoc + "'s IPXE code (for netboot)"),
// boot support
b("kernel"). b("kernel").
Produces(mime.OCTET). Produces(mime.OCTET).
Doc("Get the " + ws.hostDoc + "'s kernel (ie: for netboot)"), Doc("Get the " + ws.hostDoc + "'s kernel (ie: for netboot)"),
b("initrd"). b("initrd").
Produces(mime.OCTET). Produces(mime.OCTET).
Doc("Get the " + ws.hostDoc + "'s initial RAM disk (ie: for netboot)"), Doc("Get the " + ws.hostDoc + "'s initial RAM disk (ie: for netboot)"),
// boot v2
// - bootstrap config // - bootstrap config
b("bootstrap-config"). b("bootstrap-config").
Produces(mime.YAML). Produces(mime.YAML).
Doc("Get the " + ws.hostDoc + "'s bootstrap configuration"), Doc("Get the " + ws.hostDoc + "'s bootstrap configuration"),
b("bootstrap-config.json"). b("bootstrap-config.json").
Doc("Get the " + ws.hostDoc + "'s bootstrap configuration (as JSON)"), Doc("Get the " + ws.hostDoc + "'s bootstrap configuration (as JSON)"),
// - initrd
b("initrd-v2").
Produces(mime.OCTET).
Doc("Get the " + ws.hostDoc + "'s initial RAM disk (v2)"),
// - bootstrap // - bootstrap
b("bootstrap.tar"). b("bootstrap.tar").
Produces(mime.TAR). Produces(mime.TAR).
Doc("Get the " + ws.hostDoc + "'s bootstrap seed archive"), Doc("Get the " + ws.hostDoc + "'s bootstrap seed archive"),
b("boot-v2.iso").
Produces(mime.ISO).
Param(cmdlineParam).
Doc("Get the " + ws.hostDoc + "'s boot CD-ROM image (v2)"),
} { } {
alterRB(rb) alterRB(rb)
rws.Route(rb) rws.Route(rb)
} }
} }
func (ws *wsHost) host(req *restful.Request, resp *restful.Response) (host *localconfig.Host, cfg *localconfig.Config) { func (ws wsHost) host(req *restful.Request, resp *restful.Response) (host *localconfig.Host, cfg *localconfig.Config) {
hostname, err := ws.getHost(req) hostname, err := ws.getHost(req)
if err != nil { if err != nil {
wsError(resp, err) wsError(resp, err)
@ -121,16 +131,16 @@ func (ws *wsHost) host(req *restful.Request, resp *restful.Response) (host *loca
return return
} }
host = cfg.Host(hostname) host = hostOrTemplate(cfg, hostname)
if host == nil { if host == nil {
log.Print("no host named ", hostname)
wsNotFound(resp) wsNotFound(resp)
return return
} }
return return
} }
func (ws *wsHost) get(req *restful.Request, resp *restful.Response) { func (ws wsHost) get(req *restful.Request, resp *restful.Response) {
host, _ := ws.host(req, resp) host, _ := ws.host(req, resp)
if host == nil { if host == nil {
return return
@ -139,7 +149,7 @@ func (ws *wsHost) get(req *restful.Request, resp *restful.Response) {
resp.WriteEntity(host) resp.WriteEntity(host)
} }
func (ws *wsHost) render(req *restful.Request, resp *restful.Response) { func (ws wsHost) render(req *restful.Request, resp *restful.Response) {
host, cfg := ws.host(req, resp) host, cfg := ws.host(req, resp)
if host == nil { if host == nil {
return return
@ -171,12 +181,17 @@ func renderHost(w http.ResponseWriter, r *http.Request, what string, host *local
case "kernel": case "kernel":
err = renderKernel(w, r, ctx) err = renderKernel(w, r, ctx)
// boot v2
case "bootstrap-config":
err = renderBootstrapConfig(w, r, ctx, false)
case "bootstrap-config.json":
err = renderBootstrapConfig(w, r, ctx, true)
case "initrd": case "initrd":
err = renderCtx(w, r, ctx, what, buildInitrd) err = renderCtx(w, r, ctx, what, buildInitrd)
case "bootstrap.tar":
err = renderCtx(w, r, ctx, what, buildBootstrap)
case "boot.iso": case "boot.iso":
err = renderCtx(w, r, ctx, what, buildBootISO) err = renderCtx(w, r, ctx, what, buildBootISO)
case "boot.tar": case "boot.tar":
err = renderCtx(w, r, ctx, what, buildBootTar) err = renderCtx(w, r, ctx, what, buildBootTar)
case "boot-efi.tar": case "boot-efi.tar":
@ -184,24 +199,20 @@ func renderHost(w http.ResponseWriter, r *http.Request, what string, host *local
case "boot.img": case "boot.img":
err = renderCtx(w, r, ctx, what, buildBootImg) err = renderCtx(w, r, ctx, what, buildBootImg)
case "boot.img.gz": case "boot.img.gz":
err = renderCtx(w, r, ctx, what, buildBootImgGZ) err = renderCtx(w, r, ctx, what, buildBootImgGZ)
case "boot.img.lz4": case "boot.img.lz4":
err = renderCtx(w, r, ctx, what, buildBootImgLZ4) err = renderCtx(w, r, ctx, what, buildBootImgLZ4)
case "boot.qcow2":
// boot v2 err = renderCtx(w, r, ctx, what, qemuImgBootImg("qcow2"))
case "bootstrap-config": case "boot.qed":
err = renderBootstrapConfig(w, r, ctx, false) err = renderCtx(w, r, ctx, what, qemuImgBootImg("qed"))
case "bootstrap-config.json": case "boot.vdi":
err = renderBootstrapConfig(w, r, ctx, true) err = renderCtx(w, r, ctx, what, qemuImgBootImg("vdi"))
case "initrd-v2": case "boot.vmdk":
err = renderCtx(w, r, ctx, what, buildInitrdV2) err = renderCtx(w, r, ctx, what, qemuImgBootImg("vmdk"))
case "bootstrap.tar": case "boot.vpc":
err = renderCtx(w, r, ctx, what, buildBootstrap) err = renderCtx(w, r, ctx, what, qemuImgBootImg("vpc"))
case "boot-v2.iso":
err = renderCtx(w, r, ctx, what, buildBootISOv2)
default: default:
http.NotFound(w, r) http.NotFound(w, r)

View File

@ -0,0 +1,117 @@
package main
import (
"log"
"net/netip"
"github.com/emicklei/go-restful"
"novit.tech/direktil/pkg/localconfig"
)
var hostsFromTemplate = KVSecrets[HostFromTemplate]{"hosts-from-template"}
type HostFromTemplate struct {
Template string
IP string
}
func (hft HostFromTemplate) ClusterName(cfg *localconfig.Config) string {
for _, ht := range cfg.HostTemplates {
if ht.Name == hft.Template {
return ht.ClusterName
}
}
return ""
}
func hostOrTemplate(cfg *localconfig.Config, name string) (host *localconfig.Host) {
host = cfg.Host(name)
if host != nil {
log.Print("no host named ", name)
return
}
hft, found, err := hostsFromTemplate.Get(name)
if err != nil {
log.Print("failed to read store: ", err)
return
}
if !found {
log.Print("no host from template named ", name)
return
}
ht := cfg.HostTemplate(hft.Template)
if ht == nil {
log.Print("no host template named ", name)
return
}
host = &localconfig.Host{}
*host = *ht
host.Name = name
host.IPs = []string{hft.IP}
return
}
func wsHostsFromTemplateList(req *restful.Request, resp *restful.Response) {
hostsFromTemplate.WsList(resp, "")
}
func wsHostsFromTemplateSet(req *restful.Request, resp *restful.Response) {
name := req.PathParameter("name")
cfg, err := readConfig()
if err != nil {
wsError(resp, err)
return
}
v := HostFromTemplate{}
if err := req.ReadEntity(&v); err != nil {
wsBadRequest(resp, err.Error())
return
}
if v.Template == "" {
wsBadRequest(resp, "template is required")
return
}
if v.IP == "" {
wsBadRequest(resp, "ip is required")
return
}
if _, err := netip.ParseAddr(v.IP); err != nil {
wsBadRequest(resp, "bad IP: "+err.Error())
return
}
found := false
for _, ht := range cfg.HostTemplates {
if ht.Name != v.Template {
continue
}
found = true
break
}
if !found {
wsBadRequest(resp, "no host template with this name")
return
}
if err := hostsFromTemplate.Put(name, v); err != nil {
wsError(resp, err)
return
}
updateState()
}
func wsHostsFromTemplateDelete(req *restful.Request, resp *restful.Response) {
name := req.PathParameter("name")
hostsFromTemplate.WsDel(req, resp, name)
updateState()
}

View File

@ -5,26 +5,58 @@ import (
"bytes" "bytes"
"io" "io"
"io/fs" "io/fs"
"log"
"net/http" "net/http"
"os" "os"
"path/filepath"
restful "github.com/emicklei/go-restful" restful "github.com/emicklei/go-restful"
"m.cluseau.fr/go/httperr"
"novit.tech/direktil/local-server/secretstore"
) )
type NamedPassphrase struct {
Name string
Passphrase []byte
}
func wsUnlockStore(req *restful.Request, resp *restful.Response) { func wsUnlockStore(req *restful.Request, resp *restful.Response) {
var passphrase string np := NamedPassphrase{}
err := req.ReadEntity(&passphrase) err := req.ReadEntity(&np)
if err != nil { if err != nil {
resp.WriteError(http.StatusBadRequest, err) resp.WriteError(http.StatusBadRequest, err)
return return
} }
if err := unlockSecretStore([]byte(passphrase)); err.Any() { defer secretstore.Memzero(np.Passphrase)
if secStore.IsNew() {
if len(np.Name) == 0 {
wsBadRequest(resp, "no name given")
return
}
}
if len(np.Passphrase) == 0 {
wsBadRequest(resp, "no passphrase given")
return
}
if secStore.Unlocked() {
if secStore.HasKey(np.Passphrase) {
resp.WriteEntity(adminToken)
} else {
wsError(resp, ErrUnauthorized)
}
return
}
if err := unlockSecretStore(np.Name, np.Passphrase); err.Any() {
err.WriteJSON(resp.ResponseWriter) err.WriteJSON(resp.ResponseWriter)
return return
} }
resp.WriteEntity(*adminToken) resp.WriteEntity(adminToken)
} }
func wsStoreDownload(req *restful.Request, resp *restful.Response) { func wsStoreDownload(req *restful.Request, resp *restful.Response) {
@ -96,3 +128,68 @@ func wsStoreDownload(req *restful.Request, resp *restful.Response) {
buf.WriteTo(resp) buf.WriteTo(resp)
} }
func wsStoreUpload(req *restful.Request, resp *restful.Response) {
if !secStore.IsNew() {
wsError(resp, httperr.BadRequest("store is not new"))
return
}
buf := new(bytes.Buffer)
_, err := io.Copy(buf, req.Request.Body)
if err != nil {
wsError(resp, err)
return
}
arch := tar.NewReader(buf)
root := secStoreRoot()
for {
hdr, err := arch.Next()
if err == io.EOF {
err = nil
break
} else if err != nil {
wsError(resp, err)
return
}
log.Print(hdr.Name)
fullPath := filepath.Join(root, hdr.Name)
switch {
case hdr.FileInfo().IsDir():
err = os.MkdirAll(fullPath, 0700)
if err != nil {
wsError(resp, err)
return
}
default:
content, err := io.ReadAll(io.LimitReader(arch, hdr.Size))
if err != nil {
wsError(resp, err)
return
}
err = os.WriteFile(fullPath, content, 0600)
if err != nil {
wsError(resp, err)
return
}
}
}
if err != nil {
wsError(resp, err)
return
}
openSecretStore()
resp.WriteEntity(map[string]any{"ok": true})
}

View File

@ -1,24 +1,77 @@
package main package main
import ( import (
"strconv"
"strings"
restful "github.com/emicklei/go-restful" restful "github.com/emicklei/go-restful"
"novit.tech/direktil/local-server/secretstore"
) )
func wsStoreAddKey(req *restful.Request, resp *restful.Response) { func wsStoreAddKey(req *restful.Request, resp *restful.Response) {
var passphrase string np := NamedPassphrase{}
err := req.ReadEntity(&passphrase) err := req.ReadEntity(&np)
if err != nil { if err != nil {
wsBadRequest(resp, err.Error()) wsBadRequest(resp, err.Error())
return return
} }
if len(passphrase) == 0 { np.Name = strings.TrimSpace(np.Name)
if len(np.Name) == 0 {
wsBadRequest(resp, "no name given")
return
}
if len(np.Passphrase) == 0 {
wsBadRequest(resp, "no passphrase given") wsBadRequest(resp, "no passphrase given")
return return
} }
secStore.AddKey([]byte(passphrase)) for _, k := range secStore.Keys {
if k.Name == np.Name {
wsBadRequest(resp, "there's already a passphrase named "+strconv.Quote(np.Name))
return
}
}
secStore.AddKey(np.Name, np.Passphrase)
defer updateState()
err = secStore.SaveTo(secKeysStorePath())
if err != nil {
wsError(resp, err)
return
}
}
func wsStoreDelKey(req *restful.Request, resp *restful.Response) {
name := ""
err := req.ReadEntity(&name)
if err != nil {
wsBadRequest(resp, err.Error())
return
}
newKeys := make([]secretstore.KeyEntry, 0, len(secStore.Keys))
for _, k := range secStore.Keys {
if k.Name == name {
continue
}
newKeys = append(newKeys, k)
}
if len(newKeys) == 0 {
wsBadRequest(resp, "can't remove the last key from the store")
return
}
secStore.Keys = newKeys
defer updateState()
err = secStore.SaveTo(secKeysStorePath()) err = secStore.SaveTo(secKeysStorePath())
if err != nil { if err != nil {
wsError(resp, err) wsError(resp, err)

View File

@ -27,13 +27,16 @@ func registerWS(rest *restful.Container) {
Produces(mime.JSON). Produces(mime.JSON).
Consumes(mime.JSON). Consumes(mime.JSON).
Route(ws.POST("/unlock-store").To(wsUnlockStore). Route(ws.POST("/unlock-store").To(wsUnlockStore).
Reads(""). Reads(NamedPassphrase{}).
Writes(""). Writes("").
Doc("Try to unlock the store")). Doc("Try to unlock the store")).
Route(ws.GET("/store.tar").To(wsStoreDownload). Route(ws.GET("/store.tar").To(wsStoreDownload).
Produces(mime.TAR). Produces(mime.TAR).
Param(ws.QueryParameter("token", "the download token")). Param(ws.QueryParameter("token", "the download token")).
Doc("Fetch the encrypted store")). Doc("Fetch the encrypted store")).
Route(ws.POST("/store.tar").To(wsStoreUpload).
Consumes(mime.TAR).
Doc("Upload an existing store")).
Route(ws.GET("/downloads/{token}/{asset}").To(wsDownload). Route(ws.GET("/downloads/{token}/{asset}").To(wsDownload).
Param(ws.PathParameter("token", "the download token")). Param(ws.PathParameter("token", "the download token")).
Param(ws.PathParameter("asset", "the requested asset")). Param(ws.PathParameter("asset", "the requested asset")).
@ -43,16 +46,19 @@ func registerWS(rest *restful.Container) {
} }
// Admin-level APIs // Admin-level APIs
ws := &restful.WebService{} ws := (&restful.WebService{}).
ws. Filter(requireSecStore).
Filter(adminAuth). Filter(adminAuth).
Param(ws.HeaderParameter("Authorization", "Admin bearer token").Required(true)). Param(restful.HeaderParameter("Authorization", "Admin bearer token").Required(true)).
Produces(mime.JSON) Produces(mime.JSON)
// - store management // - store management
ws.Route(ws.POST("/store/add-key").To(wsStoreAddKey). ws.Route(ws.POST("/store/add-key").To(wsStoreAddKey).
Consumes(mime.JSON).Reads(""). Consumes(mime.JSON).Reads(NamedPassphrase{}).
Doc("Add an unlock key to the store")) Doc("Add an unlock key to the store"))
ws.Route(ws.POST("/store/delete-key").To(wsStoreDelKey).
Consumes(mime.JSON).Reads("").
Doc("Remove an unlock key to the store (by its name)"))
// - downloads // - downloads
ws.Route(ws.POST("/authorize-download").To(wsAuthorizeDownload). ws.Route(ws.POST("/authorize-download").To(wsAuthorizeDownload).
@ -70,6 +76,15 @@ func registerWS(rest *restful.Container) {
ws.Route(ws.GET("/clusters").To(wsListClusters). ws.Route(ws.GET("/clusters").To(wsListClusters).
Doc("List clusters")) Doc("List clusters"))
ws.Route(ws.GET("/hosts-from-template").To(wsHostsFromTemplateList).
Doc("List host template instances"))
ws.Route(ws.POST("/hosts-from-template/{name}").To(wsHostsFromTemplateSet).
Reads(HostFromTemplate{}).
Doc("Create or update a host template instance"))
ws.Route(ws.DELETE("/hosts-from-template/{name}").To(wsHostsFromTemplateDelete).
Reads(HostFromTemplate{}).
Doc("Delete a host template instance"))
const ( const (
GET = http.MethodGet GET = http.MethodGet
PUT = http.MethodPut PUT = http.MethodPut
@ -118,8 +133,20 @@ func registerWS(rest *restful.Container) {
ws.Route(ws.GET("/hosts").To(wsListHosts). ws.Route(ws.GET("/hosts").To(wsListHosts).
Doc("List hosts")) Doc("List hosts"))
ws.Route(ws.GET("/ssh-acls").To(wsSSH_ACL_List))
ws.Route(ws.GET("/ssh-acls/{acl-name}").To(wsSSH_ACL_Get))
ws.Route(ws.PUT("/ssh-acls/{acl-name}").To(wsSSH_ACL_Set))
rest.Add(ws)
// Hosts API
ws = (&restful.WebService{}).
Filter(requireSecStore).
Filter(adminAuth).
Path("/hosts/{host-name}").
Param(ws.HeaderParameter("Authorization", "Host or admin bearer token"))
(&wsHost{ (&wsHost{
prefix: "/hosts/{host-name}",
hostDoc: "given host", hostDoc: "given host",
getHost: func(req *restful.Request) (string, error) { getHost: func(req *restful.Request) (string, error) {
return req.PathParameter("host-name"), nil return req.PathParameter("host-name"), nil
@ -128,17 +155,12 @@ func registerWS(rest *restful.Container) {
rb.Param(ws.PathParameter("host-name", "host's name")) rb.Param(ws.PathParameter("host-name", "host's name"))
}) })
ws.Route(ws.GET("/ssh-acls").To(wsSSH_ACL_List))
ws.Route(ws.GET("/ssh-acls/{acl-name}").To(wsSSH_ACL_Get))
ws.Route(ws.PUT("/ssh-acls/{acl-name}").To(wsSSH_ACL_Set))
rest.Add(ws) rest.Add(ws)
// Hosts API // Detected host API
ws = &restful.WebService{} ws = (&restful.WebService{}).
ws.Produces(mime.JSON). Filter(requireSecStore).
Path("/me"). Path("/me").
Filter(hostsAuth).
Param(ws.HeaderParameter("Authorization", "Host or admin bearer token")) Param(ws.HeaderParameter("Authorization", "Host or admin bearer token"))
(&wsHost{ (&wsHost{
@ -149,8 +171,10 @@ func registerWS(rest *restful.Container) {
}) })
// Hosts by token API // Hosts by token API
ws = &restful.WebService{} ws = (&restful.WebService{}).
ws.Path("/hosts-by-token/{host-token}").Param(ws.PathParameter("host-token", "host's download token")) Filter(requireSecStore).
Path("/hosts-by-token/{host-token}").
Param(ws.PathParameter("host-token", "host's download token"))
(&wsHost{ (&wsHost{
hostDoc: "token's host", hostDoc: "token's host",
@ -178,7 +202,19 @@ func registerWS(rest *restful.Container) {
rest.Add(ws) rest.Add(ws)
} }
func requireSecStore(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
if !secStore.Unlocked() {
wsError(resp, ErrStoreLocked)
return
}
chain.ProcessFilter(req, resp)
}
func detectHost(req *restful.Request) (hostName string, err error) { func detectHost(req *restful.Request) (hostName string, err error) {
if !*allowDetectedHost {
return
}
r := req.Request r := req.Request
remoteAddr := r.RemoteAddr remoteAddr := r.RemoteAddr

73
go.mod
View File

@ -1,49 +1,51 @@
module novit.tech/direktil/local-server module novit.tech/direktil/local-server
go 1.20 go 1.21
require ( require (
github.com/cavaliergopher/cpio v1.0.1
github.com/cespare/xxhash v1.1.0 github.com/cespare/xxhash v1.1.0
github.com/cloudflare/cfssl v1.6.4 github.com/cloudflare/cfssl v1.6.5
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/emicklei/go-restful v2.16.0+incompatible github.com/emicklei/go-restful v2.16.0+incompatible
github.com/emicklei/go-restful-openapi v1.4.1 github.com/emicklei/go-restful-openapi v1.4.1
github.com/go-git/go-git/v5 v5.6.1 github.com/go-git/go-git/v5 v5.12.0
github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34 github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34
github.com/miolini/datacounter v1.0.3 github.com/miolini/datacounter v1.0.3
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/pierrec/lz4 v2.6.1+incompatible github.com/pierrec/lz4 v2.6.1+incompatible
golang.org/x/crypto v0.8.0 github.com/sergeymakinen/go-crypt v1.0.0
golang.org/x/crypto v0.22.0
gopkg.in/src-d/go-billy.v4 v4.3.2 gopkg.in/src-d/go-billy.v4 v4.3.2
gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
k8s.io/apimachinery v0.27.1 k8s.io/apimachinery v0.29.3
m.cluseau.fr/go v0.0.0-20230406121934-7940120eb250 m.cluseau.fr/go v0.0.0-20230809064045-12c5a121c766
novit.tech/direktil/pkg v0.0.0-20230201224712-5e39572dc50e novit.tech/direktil/pkg v0.0.0-20240415130406-0d2e181a4ed6
) )
replace github.com/zmap/zlint/v3 => github.com/zmap/zlint/v3 v3.3.1 replace github.com/zmap/zlint/v3 => github.com/zmap/zlint/v3 v3.3.1
require ( require (
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/cloudflare/circl v1.3.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/frankban/quicktest v1.5.0 // indirect github.com/frankban/quicktest v1.5.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/logr v1.4.1 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/gobuffalo/envy v1.10.2 // indirect github.com/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect github.com/gobuffalo/packr v1.30.1 // indirect
github.com/google/certificate-transparency-go v1.1.5 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/imdario/mergo v0.3.15 // indirect github.com/google/certificate-transparency-go v1.1.8 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.5.1 // indirect
@ -56,23 +58,24 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect github.com/skeema/knownhosts v1.2.2 // indirect
github.com/src-d/gcfg v1.4.0 // indirect github.com/src-d/gcfg v1.4.0 // indirect
github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db // indirect github.com/weppos/publicsuffix-go v0.30.2 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 // indirect github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c // indirect
github.com/zmap/zlint/v3 v3.1.0 // indirect github.com/zmap/zlint/v3 v3.5.0 // indirect
golang.org/x/mod v0.10.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.9.0 // indirect golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect golang.org/x/sys v0.19.0 // indirect
golang.org/x/tools v0.8.0 // indirect golang.org/x/text v0.14.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect golang.org/x/tools v0.20.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.100.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect
) )

293
go.sum
View File

@ -1,4 +1,6 @@
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
@ -6,12 +8,10 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 h1:L6S7kR7SlhQKplIBpkra3s6yhcZV51lhRnXmYc4HohI= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
@ -21,26 +21,34 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8= github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8=
github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0= github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0=
github.com/cloudflare/cfssl v1.6.5 h1:46zpNkm6dlNkMZH/wMW22ejih6gIaJbzL2du6vD7ZeI=
github.com/cloudflare/cfssl v1.6.5/go.mod h1:Bk1si7sq8h2+yVEDrFJiz3d7Aw+pfjjJSZVaD+Taky4=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.3.2 h1:VWp8dY3yH69fdM7lM6A1+NhhVoDu9vqK0jOgmkQHFWk= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.16.0+incompatible h1:rgqiKNjTnFQA6kkhFe16D8epTksy9HQ1MyrbDXSdYhM= github.com/emicklei/go-restful v2.16.0+incompatible h1:rgqiKNjTnFQA6kkhFe16D8epTksy9HQ1MyrbDXSdYhM=
github.com/emicklei/go-restful v2.16.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.16.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
@ -49,8 +57,8 @@ github.com/emicklei/go-restful-openapi v1.4.1/go.mod h1:kWQ8rQMVQ6G6lePwjDveJ00K
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3gQVY= github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3gQVY=
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
@ -58,37 +66,42 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.0.0-20180322222829-3a0015ad55fa/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.0.0-20180322222829-3a0015ad55fa/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.0.0-20180322222742-3fb327e6747d/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.0.0-20180322222742-3fb327e6747d/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.0.0-20180415031709-bcff419492ee/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.0.0-20180415031709-bcff419492ee/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.0.0-20180405201759-811b1089cde9/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.0.0-20180405201759-811b1089cde9/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
@ -100,31 +113,32 @@ github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/certificate-transparency-go v1.1.5 h1:EVfYyOiMSdwwXd6FJxnh0jYgYj/Dh5n9sXtgIr5+Vj0= github.com/google/certificate-transparency-go v1.1.7 h1:IASD+NtgSTJLPdzkthwvAG1ZVbF2WtFg4IvoA68XGSw=
github.com/google/certificate-transparency-go v1.1.5/go.mod h1:CnNCSPt9ptZQ8jDSrqyTmh2dT2MQLKymfGYwXqjQ7YY= github.com/google/certificate-transparency-go v1.1.7/go.mod h1:FSSBo8fyMVgqptbfF6j5p/XNdgQftAhSmXcIxV9iphE=
github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to=
github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
@ -144,26 +158,28 @@ github.com/kisielk/sqlstruct v0.0.0-20210630145711-dae28ed37023/go.mod h1:yyMNCy
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20180323154445-8b799c424f57/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180323154445-8b799c424f57/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34 h1:F3u4DKQ4T30mlBNFmSGzTqdkmVqbfVORv34ZRvc7PuE= github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34 h1:F3u4DKQ4T30mlBNFmSGzTqdkmVqbfVORv34ZRvc7PuE=
github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34/go.mod h1:lcyE8C83VRamH/oTpikU4+yVCCxLthWgDOqjHSsu+ZY= github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34/go.mod h1:lcyE8C83VRamH/oTpikU4+yVCCxLthWgDOqjHSsu+ZY=
github.com/miolini/datacounter v1.0.3 h1:tanOZPVblGXQl7/bSZWoEM8l4KK83q24qwQLMrO/HOA= github.com/miolini/datacounter v1.0.3 h1:tanOZPVblGXQl7/bSZWoEM8l4KK83q24qwQLMrO/HOA=
@ -171,7 +187,6 @@ github.com/miolini/datacounter v1.0.3/go.mod h1:C45dc2hBumHjDpEU64IqPwR6TDyPVpzO
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -179,9 +194,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -200,20 +216,27 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergeymakinen/go-crypt v1.0.0 h1:R9yyekVU0bxP7MUUJd3uCcGQxg5RqMJ/ahy2bllx9qg=
github.com/sergeymakinen/go-crypt v1.0.0/go.mod h1:5NPTCUNfQrNlck+I9ONHFT55UvGWzosSf+QQPSuxYBE=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@ -228,22 +251,23 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db h1:/WcxBne+5CbtbgWd/sV2wbravmr4sT7y52ifQaCgoLs= github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222 h1:h2JizvZl9aIj6za9S5AyrkU+OzIS4CetQthH/ejO+lg=
github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db/go.mod h1:aiQaH1XpzIfgrJq3S1iw7w+3EDbRP7mF5fmwUhWyRUs= github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ=
github.com/weppos/publicsuffix-go v0.30.2 h1:Np18yzfMR90jNampWFs7iSh2sw/qCZkhL41/ffyihCU=
github.com/weppos/publicsuffix-go v0.30.2/go.mod h1:/hGscit36Yt+wammfBBwdMdxBT8btsTt6KvwO9OvMyM=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
@ -255,11 +279,10 @@ github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54t
github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk= github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk=
github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
github.com/zmap/zcrypto v0.0.0-20220402174210-599ec18ecbac/go.mod h1:egdRkzUylATvPkWMpebZbXhv0FMEMJGX/ur0D3Csk2s= github.com/zmap/zcrypto v0.0.0-20220402174210-599ec18ecbac/go.mod h1:egdRkzUylATvPkWMpebZbXhv0FMEMJGX/ur0D3Csk2s=
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 h1:YOQ1vXEwE4Rnj+uQ/3oCuJk5wgVsvUyW+glsndwYuyA= github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c h1:U1b4THKcgOpJ+kILupuznNwPiURtwVW3e9alJvji9+s=
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968/go.mod h1:xIuOvYCZX21S5Z9bK1BMrertTGX/F8hgAPw7ERJRNS0= github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c/go.mod h1:GSDpFDD4TASObxvfZfvpZZ3OWHIUHMlhVWlkOe4ewVk=
github.com/zmap/zlint/v3 v3.3.1 h1:IrIY2Qd2Wr9ZHhdQ3mszehSydz+x6OROClztMEK+2bU= github.com/zmap/zlint/v3 v3.3.1 h1:IrIY2Qd2Wr9ZHhdQ3mszehSydz+x6OROClztMEK+2bU=
github.com/zmap/zlint/v3 v3.3.1/go.mod h1:fPCW5acxhqw4HU1Vm0t9oFEPo1/uH9hI0sci/Z++hEI= github.com/zmap/zlint/v3 v3.3.1/go.mod h1:fPCW5acxhqw4HU1Vm0t9oFEPo1/uH9hI0sci/Z++hEI=
golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -269,23 +292,25 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -299,19 +324,24 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -321,11 +351,9 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -333,22 +361,35 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -357,32 +398,37 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@ -396,24 +442,27 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8=
k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM= k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
m.cluseau.fr/go v0.0.0-20230406121934-7940120eb250 h1:vSITZdoUVqlJBAOzCuC/Pz2GNM6Mgi/YOJ/ALmlPFd0= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ=
m.cluseau.fr/go v0.0.0-20230406121934-7940120eb250/go.mod h1:dOJ2bB14TFegivjXAccmGm+S9ZlNBRtuQq8tEqPPvJM= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
novit.nc/direktil/pkg v0.0.0-20220221171542-fd3ce3a1491b/go.mod h1:zwTVO6U0tXFEaga73megQIBK7yVIKZJVePaIh/UtdfU= k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY=
novit.tech/direktil/pkg v0.0.0-20230201224712-5e39572dc50e h1:eQFbzcuB4wOSrnOhkcN30hFDCIack40VkIoqVRbWnWc= k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
novit.tech/direktil/pkg v0.0.0-20230201224712-5e39572dc50e/go.mod h1:2Mir5x1eT/e295WeFGzzXa4siunKX4z+rmNPfVsXS0k= m.cluseau.fr/go v0.0.0-20230809064045-12c5a121c766 h1:JRzMBDbUwrTTGDJaJSH0ap4vRL0Q9CN1bG8a6n49eaQ=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= m.cluseau.fr/go v0.0.0-20230809064045-12c5a121c766/go.mod h1:BMv3aOSYpupuiiG3Ch3ND88aB5CfAks3YZuRLE8j1ls=
novit.tech/direktil/pkg v0.0.0-20240120172717-8498a102796f h1:7y11nLhChrrsLQwRaW7wn/9x+Xn2gEVtzj75VkOpJ+o=
novit.tech/direktil/pkg v0.0.0-20240120172717-8498a102796f/go.mod h1:zjezU6tELE880oYHs/WAauGBupKIEQQ7KqWTB69RW10=
novit.tech/direktil/pkg v0.0.0-20240415114750-ccdbf2e2a5e4 h1:8HviBvbPzIBo2VpDl2jc6iPwiqlOrAbXqRhtn/yYMYE=
novit.tech/direktil/pkg v0.0.0-20240415114750-ccdbf2e2a5e4/go.mod h1:zjezU6tELE880oYHs/WAauGBupKIEQQ7KqWTB69RW10=
novit.tech/direktil/pkg v0.0.0-20240415130406-0d2e181a4ed6 h1:D0TN5GyZ4d88ILpgVZgcZ62027lW8/LLnQSpQyN2yOw=
novit.tech/direktil/pkg v0.0.0-20240415130406-0d2e181a4ed6/go.mod h1:zjezU6tELE880oYHs/WAauGBupKIEQQ7KqWTB69RW10=

View File

@ -1,8 +0,0 @@
export GOVC_DATACENTER=
export GOVC_PASSWORD=
export GOVC_URL=
export GOVC_USERNAME=
export GOVC_INSECURE=1
export GOVC_DATASTORE=
export NOVIT_VM_FOLDER=
export NOVIT_ISO_FOLDER=

3
hack/build Executable file
View File

@ -0,0 +1,3 @@
#! /bin/sh
set -ex
go build -o dist/ -trimpath -ldflags "-X main.Version=${GIT_TAG:-$(git describe --always --dirty)}" $*

8
hack/docker-build Executable file
View File

@ -0,0 +1,8 @@
#! /bin/bash
set -ex
case "$1" in
commit) tag=$(git describe --always --dirty) ;;
"") tag=latest ;;
*) tag=$1 ;;
esac
docker build -t novit.tech/direktil/local-server:$tag .

4
hack/install Executable file
View File

@ -0,0 +1,4 @@
#! /bin/sh
set -ex
go install -trimpath -ldflags "-X main.Version=$(git describe --always --dirty)" \
./cmd/dkl-dir2config

View File

@ -21,21 +21,3 @@
.cluster { .cluster {
max-width: 50%; max-width: 50%;
} }
#store-infos {
display: flex;
flex-flow: row wrap;
align-content: center;
justify-content: flex-start;
border-bottom: dashed 1pt;
margin-bottom: 1ex;
}
#store-infos > * {
display: block;
font-size: medium;
padding: 2pt 1ex;
margin: 0 0 0 1ex;
}
#store-infos > *:first-child {
margin-left: 0;
}

View File

@ -22,7 +22,8 @@
<button class="link" @click="copyText(session.token)">&#x1F5D0;</button> <button class="link" @click="copyText(session.token)">&#x1F5D0;</button>
</span> </span>
<span id="uiHash">ui <code>{{ uiHash || '-----' }}</code></span> <span>server <code>{{ serverVersion || '-----' }}</code></span>
<span>ui <code>{{ uiHash || '-----' }}</code></span>
<span :class="publicState ? 'green' : 'red'">&#x1F5F2;</span> <span :class="publicState ? 'green' : 'red'">&#x1F5F2;</span>
</div> </div>
@ -39,16 +40,23 @@
</template> </template>
<template v-else-if="publicState.Store.New"> <template v-else-if="publicState.Store.New">
<p>Store is new.</p> <p>Store is new.</p>
<form @submit="unlockStore" action="/public/unlock-store"> <p>Option 1: initialize a new store</p>
<input type="password" v-model="forms.store.pass1" name="passphrase" required /> <form @submit="unlockStore">
<input type="password" v-model="forms.store.pass2" required /> <input type="text" v-model="forms.store.name" name="name" placeholder="Name" /><br/>
<input type="password" v-model="forms.store.pass1" name="passphrase" required placeholder="Passphrase" />
<input type="password" v-model="forms.store.pass2" required placeholder="Passphrase confirmation" />
<input type="submit" value="initialize" :disabled="!forms.store.pass1 || forms.store.pass1 != forms.store.pass2" /> <input type="submit" value="initialize" :disabled="!forms.store.pass1 || forms.store.pass1 != forms.store.pass2" />
</form> </form>
<p>Option 2: upload a previously downloaded store</p>
<form @submit="uploadStore">
<input type="file" ref="storeUpload" />
<input type="submit" value="upload" />
</form>
</template> </template>
<template v-else-if="!publicState.Store.Open"> <template v-else-if="!publicState.Store.Open">
<p>Store is not open.</p> <p>Store is not open.</p>
<form @submit="unlockStore" action="/public/unlock-store"> <form @submit="unlockStore">
<input type="password" name="passphrase" v-model="forms.store.pass1" required /> <input type="password" name="passphrase" v-model="forms.store.pass1" required placeholder="Passphrase" />
<input type="submit" value="unlock" :disabled="!forms.store.pass1" /> <input type="submit" value="unlock" :disabled="!forms.store.pass1" />
</form> </form>
</template> </template>
@ -56,24 +64,13 @@
<p v-if="!session.token">Not logged in.</p> <p v-if="!session.token">Not logged in.</p>
<p v-else>Invalid token</p> <p v-else>Invalid token</p>
<form @submit="setToken"> <form @submit="unlockStore">
<input type="password" v-model="forms.setToken" required /> <input type="password" v-model="forms.store.pass1" required placeholder="Passphrase" />
<input type="submit" value="set token"/> <input type="submit" value="log in"/>
</form> </form>
</template> </template>
<template v-else> <template v-else>
<div id="store-infos">
<h2>Store</h2>
<a :href="'/public/store.tar?token='+state.Store.DownloadToken" target="_blank">download</a>
<form @submit="storeAddKey" action="/store/add-key">
<input type="password" v-model="forms.store.pass1" name="passphrase" autocomplete="new-password" required />
<input type="password" v-model="forms.store.pass2" autocomplete="new-password" required />
<input type="submit" value="add unlock phrase" :disabled="!forms.store.pass1 || forms.store.pass1 != forms.store.pass2" />
</form>
</div>
<div v-if="state.Clusters" id="clusters"> <div v-if="state.Clusters" id="clusters">
<h2>Clusters</h2> <h2>Clusters</h2>
<div class="sheets"> <div class="sheets">
@ -89,7 +86,52 @@
</div> </div>
</div> </div>
<pre v-if="false">{{ state }}</pre> <h2>Admin actions</h2>
<h3>Config</h3>
<form @submit="uploadConfig">
<input type="file" ref="configUpload" required />
<input type="submit" value="upload config" />
</form>
<h3>Store</h3>
<p><a :href="'/public/store.tar?token='+state.Store.DownloadToken" target="_blank">Download</a></p>
<form @submit="storeAddKey" action="/store/add-key">
<p>Add an unlock phrase:</p>
<input type="text" v-model="forms.store.name" name="name" required placeholder="Name" /><br/>
<input type="password" v-model="forms.store.pass1" name="passphrase" autocomplete="new-password" required placeholder="Phrase" />
<input type="password" v-model="forms.store.pass2" autocomplete="new-password" required placeholder="Phrase confirmation" />
<input type="submit" value="add unlock phrase" :disabled="!forms.store.pass1 || forms.store.pass1 != forms.store.pass2" />
</form>
<form @submit="storeDelKey" action="/store/delete-key">
<p>Remove an unlock phrase:</p>
<input type="text" v-model="forms.delKey.name" name="name" required placeholder="Name" />
<input type="submit" value="remove unlock phrase" />
<p v-if="state.Store.KeyNames">Available names:
<template v-for="k,i in state.Store.KeyNames">{{i?", ":""}}<code @click="forms.delKey.name=k">{{k}}</code></template>.</p>
</form>
<template v-if="state.HostTemplates && state.HostTemplates.length">
<h3>Hosts from template</h3>
<form @submit="hostFromTemplateAdd" action="">
<p>Add a host from template instance:</p>
<input type="text" v-model="forms.hostFromTemplate.name" required placeholder="Name" />
<select v-model="forms.hostFromTemplate.Template" required>
<option v-for="name in state.HostTemplates" :value="name">{{name}}</option>
</select>
<input type="text" v-model="forms.hostFromTemplate.IP" required placeholder="IP" />
<input type="submit" value="add instance" />
</form>
<form @submit="hostFromTemplateDel" action="">
<p>Remove a host from template instance:</p>
<select v-model="forms.hostFromTemplateDel" required>
<option v-for="h in hostsFromTemplate" :value="h.Name">{{h.Name}}</option>
</select>
<input type="submit" value="delete instance" :disabled="!forms.hostFromTemplateDel" />
</form>
</template>
</template> </template>
</div> </div>

View File

@ -10,18 +10,18 @@ export default {
cluster: ['addons'], cluster: ['addons'],
host: [ host: [
"kernel", "kernel",
"initrd-v2", "initrd",
"bootstrap.tar", "bootstrap.tar",
"boot-v2.iso", "boot.img.lz4",
"boot.iso",
"config", "config",
"bootstrap-config", "bootstrap-config",
"boot.iso",
"boot.tar", "boot.tar",
"boot-efi.tar", "boot-efi.tar",
"boot.img",
"boot.img.gz", "boot.img.gz",
"boot.img.lz4", "boot.img",
"initrd", "boot.qcow2",
"boot.vmdk",
"ipxe", "ipxe",
], ],
}[this.kind] }[this.kind]

View File

@ -8,8 +8,9 @@ export default {
<div class="host"> <div class="host">
<div class="title">Host {{ host.Name }}</div> <div class="title">Host {{ host.Name }}</div>
<section> <section>
<div><small>Cluster: {{ host.Cluster }}<template v-if="host.Template"> ({{ host.Template }})</template></small></div>
<template v-for="ip in host.IPs"> <template v-for="ip in host.IPs">
{{ ip }} <code>{{ ip }}</code>{{" "}}
</template> </template>
</section> </section>
<div class="section">Downloads</div> <div class="section">Downloads</div>

View File

@ -10,10 +10,15 @@ createApp({
return { return {
forms: { forms: {
store: {}, store: {},
storeUpload: {},
delKey: {},
hostFromTemplate: {},
hostFromTemplateDel: "",
}, },
session: {}, session: {},
error: null, error: null,
publicState: null, publicState: null,
serverVersion: null,
uiHash: null, uiHash: null,
watchingState: false, watchingState: false,
state: null, state: null,
@ -39,6 +44,7 @@ createApp({
deep: true, deep: true,
handler(v) { handler(v) {
if (v) { if (v) {
this.serverVersion = v.ServerVersion
if (this.uiHash && v.UIHash != this.uiHash) { if (this.uiHash && v.UIHash != this.uiHash) {
console.log("reloading") console.log("reloading")
location.reload() location.reload()
@ -50,6 +56,12 @@ createApp({
} }
}, },
computed: {
hostsFromTemplate() {
return (this.state.Hosts||[]).filter((h) => h.Template)
},
},
methods: { methods: {
copyText(text) { copyText(text) {
event.preventDefault() event.preventDefault()
@ -60,13 +72,34 @@ createApp({
this.session.token = this.forms.setToken this.session.token = this.forms.setToken
this.forms.setToken = null this.forms.setToken = null
}, },
uploadStore() {
event.preventDefault()
this.apiPost('/public/store.tar', this.$refs.storeUpload.files[0], (v) => {
this.forms.store = {}
}, "application/tar")
},
namedPassphrase(name, passphrase) {
return {Name: this.forms.store.name, Passphrase: btoa(this.forms.store.pass1)}
},
storeAddKey() { storeAddKey() {
this.apiPost('/store/add-key', this.forms.store.pass1, (v) => { this.apiPost('/store/add-key', this.namedPassphrase(), (v) => {
this.forms.store = {} this.forms.store = {}
}) })
}, },
storeDelKey() {
event.preventDefault()
let name = this.forms.delKey.name
if (!confirm("Remove key named "+JSON.stringify(name)+"?")) {
return
}
this.apiPost('/store/delete-key', name , (v) => {
this.forms.delKey = {}
})
},
unlockStore() { unlockStore() {
this.apiPost('/public/unlock-store', this.forms.store.pass1, (v) => { this.apiPost('/public/unlock-store', this.namedPassphrase(), (v) => {
this.forms.store = {} this.forms.store = {}
if (v) { if (v) {
@ -78,7 +111,23 @@ createApp({
} }
}) })
}, },
apiPost(action, data, onload) { uploadConfig() {
this.apiPost('/configs', this.$refs.configUpload.files[0], (v) => {}, "text/vnd.yaml")
},
hostFromTemplateAdd() {
let v = this.forms.hostFromTemplate;
this.apiPost('/hosts-from-template/'+v.name, v, (v) => { this.forms.hostFromTemplate = {} });
},
hostFromTemplateDel() {
event.preventDefault()
let v = this.forms.hostFromTemplateDel;
if (!confirm("delete host template instance "+v+"?")) {
return
}
this.apiDelete('/hosts-from-template/'+v, (v) => { this.forms.hostFromTemplateDel = "" });
},
apiPost(action, data, onload, contentType = 'application/json') {
event.preventDefault() event.preventDefault()
if (data === undefined) { if (data === undefined) {
@ -97,7 +146,7 @@ createApp({
var xhr = new XMLHttpRequest() var xhr = new XMLHttpRequest()
xhr.responseType = 'json' xhr.responseType = 'json'
// TODO spinner, pending aciton notification, or something // TODO spinner, pending action notification, or something
xhr.onerror = () => { xhr.onerror = () => {
// this.actionResults.splice(idx, 1, {...item, done: true, failed: true }) // this.actionResults.splice(idx, 1, {...item, done: true, failed: true })
} }
@ -115,11 +164,37 @@ createApp({
xhr.open("POST", action) xhr.open("POST", action)
xhr.setRequestHeader('Accept', 'application/json') xhr.setRequestHeader('Accept', 'application/json')
xhr.setRequestHeader('Content-Type', 'application/json') xhr.setRequestHeader('Content-Type', contentType)
if (this.session.token) { if (this.session.token) {
xhr.setRequestHeader('Authorization', 'Bearer '+this.session.token) xhr.setRequestHeader('Authorization', 'Bearer '+this.session.token)
} }
if (contentType == "application/json") {
xhr.send(JSON.stringify(data)) xhr.send(JSON.stringify(data))
} else {
xhr.send(data)
}
},
apiDelete(action, data, onload) {
event.preventDefault()
var xhr = new XMLHttpRequest()
xhr.onload = (r) => {
if (xhr.status != 200) {
this.error = xhr.response
return
}
this.error = null
if (onload) {
onload(xhr.response)
}
}
xhr.open("DELETE", action)
xhr.setRequestHeader('Accept', 'application/json')
if (this.session.token) {
xhr.setRequestHeader('Authorization', 'Bearer '+this.session.token)
}
xhr.send()
}, },
download(url) { download(url) {
event.target.target = '_blank' event.target.target = '_blank'

View File

@ -3,24 +3,24 @@ modd.conf {}
**/*.go go.mod go.sum { **/*.go go.mod go.sum {
prep: go test ./... prep: go test ./...
prep: mkdir -p dist prep: mkdir -p dist
prep: go build -o dist/ -trimpath ./... prep: hack/build ./...
#prep: docker build --build-arg GOPROXY=$GOPROXY -t dls . #prep: docker build --build-arg GOPROXY=$GOPROXY -t dls .
#daemon +sigterm: /var/lib/direktil/test-run #daemon +sigterm: bash test-run
} }
html/**/* { html/**/* {
prep: go build -o dist/ -trimpath ./cmd/dkl-local-server prep: hack/build ./cmd/dkl-local-server
} }
dist/dkl-local-server { #dist/dkl-local-server {
prep: mkdir -p tmp # prep: mkdir -p tmp
daemon +sigterm: dist/dkl-local-server -data tmp -auto-unlock test # daemon +sigterm: dist/dkl-local-server -data tmp -auto-unlock test
} #}
dist/dkl-dir2config { dist/dkl-dir2config {
prep: dist/dkl-dir2config --debug --in test-dir2config # prep: dist/dkl-dir2config --debug --in test-dir2config
} }
**/*.proto !dist/**/* { #**/*.proto !dist/**/* {
prep: for mod in @mods; do protoc -I ./ --go_out=plugins=grpc,paths=source_relative:. $mod; done # prep: for mod in @mods; do protoc -I ./ --go_out=plugins=grpc,paths=source_relative:. $mod; done
} #}

View File

@ -4,7 +4,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net" "net"
"os" "os"
@ -27,8 +26,7 @@ type Config struct {
Hosts []*Host Hosts []*Host
Clusters []*Cluster Clusters []*Cluster
Configs []*Template Configs []*Template
StaticPods []*Template `yaml:"static_pods"` StaticPods map[string][]*Template `yaml:"static_pods"`
BootstrapPods map[string][]*Template `yaml:"bootstrap_pods"`
Addons map[string][]*Template Addons map[string][]*Template
SSLConfig string `yaml:"ssl_config"` SSLConfig string `yaml:"ssl_config"`
CertRequests []*CertRequest `yaml:"cert_requests"` CertRequests []*CertRequest `yaml:"cert_requests"`
@ -43,7 +41,7 @@ func FromBytes(data []byte) (*Config, error) {
} }
func FromFile(path string) (*Config, error) { func FromFile(path string) (*Config, error) {
ba, err := ioutil.ReadFile(path) ba, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -121,7 +119,7 @@ func (c *Config) SaveTo(path string) error {
return err return err
} }
return ioutil.WriteFile(path, ba, 0600) return os.WriteFile(path, ba, 0600)
} }
type Template struct { type Template struct {
@ -135,6 +133,11 @@ func (t *Template) Execute(contextName, elementName string, wr io.Writer, data i
indented = indent + strings.Replace(s, "\n", "\n"+indent, -1) indented = indent + strings.Replace(s, "\n", "\n"+indent, -1)
return return
}, },
"yaml": func(v any) (s string, err error) {
ba, err := yaml.Marshal(v)
s = string(ba)
return
},
} }
for name, f := range extraFuncs { for name, f := range extraFuncs {
@ -157,7 +160,7 @@ func (t *Template) Execute(contextName, elementName string, wr io.Writer, data i
base += string(filepath.Separator) base += string(filepath.Separator)
log.Print("writing template details: ", base, "{in,data,out}") log.Print("writing template details: ", base, "{in,data,out}")
if err := ioutil.WriteFile(base+"in", []byte(t.Template), 0600); err != nil { if err := os.WriteFile(base+"in", []byte(t.Template), 0600); err != nil {
return err return err
} }
@ -166,7 +169,7 @@ func (t *Template) Execute(contextName, elementName string, wr io.Writer, data i
return err return err
} }
if err := ioutil.WriteFile(base+"data", yamlBytes, 0600); err != nil { if err := os.WriteFile(base+"data", yamlBytes, 0600); err != nil {
return err return err
} }
@ -187,24 +190,29 @@ func (t *Template) Execute(contextName, elementName string, wr io.Writer, data i
type Host struct { type Host struct {
WithRev WithRev
Name string Template bool `json:",omitempty"`
Labels map[string]string
Annotations map[string]string
MAC string Name string
Labels map[string]string `json:",omitempty"`
Annotations map[string]string `json:",omitempty"`
MAC string `json:",omitempty"`
IP string IP string
IPs []string IPs []string `json:",omitempty"`
Cluster string Cluster string
Group string Group string
IPXE string Net string
IPFrom map[string]string `json:",omitempty" yaml:"ip_from"`
IPXE string `json:",omitempty"`
Kernel string Kernel string
Initrd string Initrd string
BootstrapConfig string `yaml:"bootstrap_config"` BootstrapConfig string `yaml:"bootstrap_config"`
Config string Config string
Versions map[string]string Versions map[string]string
BootstrapPods string `yaml:"bootstrap_pods"` StaticPods string `yaml:"static_pods"`
Vars Vars Vars Vars
} }
@ -221,7 +229,7 @@ type Cluster struct {
Annotations map[string]string Annotations map[string]string
Domain string Domain string
Addons string Addons []string
Subnets struct { Subnets struct {
Services string Services string
Pods string Pods string

View File

@ -35,7 +35,7 @@ func FromDir(
config := &Config{ config := &Config{
Addons: make(map[string][]*Template), Addons: make(map[string][]*Template),
BootstrapPods: make(map[string][]*Template), StaticPods: make(map[string][]*Template),
} }
// load clusters // load clusters
@ -102,11 +102,12 @@ func FromDir(
// cluster addons // cluster addons
for _, cluster := range config.Clusters { for _, cluster := range config.Clusters {
addonSet := cluster.Addons addonSets := cluster.Addons
if len(addonSet) == 0 { if len(addonSets) == 0 {
continue continue
} }
for _, addonSet := range addonSets {
if _, ok := config.Addons[addonSet]; ok { if _, ok := config.Addons[addonSet]; ok {
continue continue
} }
@ -118,24 +119,25 @@ func FromDir(
config.Addons[addonSet] = templates config.Addons[addonSet] = templates
} }
}
// cluster bootstrap pods // cluster static pods
for _, host := range config.Hosts { for _, host := range config.Hosts {
bpSet := host.BootstrapPods bpSet := host.StaticPods
if bpSet == "" { if bpSet == "" {
continue continue
} }
if _, ok := config.BootstrapPods[bpSet]; ok { if _, ok := config.StaticPods[bpSet]; ok {
continue continue
} }
templates := make([]*Template, 0) templates := make([]*Template, 0)
if err = loadTemplates(path.Join("bootstrap-pods", bpSet), &templates); err != nil { if err = loadTemplates(path.Join("static-pods", bpSet), &templates); err != nil {
return nil, err return nil, err
} }
config.BootstrapPods[bpSet] = templates config.StaticPods[bpSet] = templates
} }
// load SSL configuration // load SSL configuration

View File

@ -1,5 +1,7 @@
package secretstore package secretstore
func Memzero(ba []byte) { memzero(ba) }
func memzero(ba []byte) { func memzero(ba []byte) {
for i := range ba { for i := range ba {
ba[i] = 0 ba[i] = 0

View File

@ -2,29 +2,34 @@ package secretstore
import ( import (
"bufio" "bufio"
"bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/sha512" "crypto/sha512"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log" "log"
"os" "os"
"strconv"
"syscall" "syscall"
"golang.org/x/crypto/argon2" "golang.org/x/crypto/argon2"
) )
type Store struct { type Store struct {
Salt [aes.BlockSize]byte
Keys []KeyEntry
unlocked bool unlocked bool
key [32]byte key [32]byte
salt [aes.BlockSize]byte
keys []keyEntry
} }
type keyEntry struct { type KeyEntry struct {
hash [64]byte Name string
encKey [32]byte Hash [64]byte
EncKey [32]byte
} }
func New() (s *Store) { func New() (s *Store) {
@ -77,30 +82,32 @@ func (s *Store) Close() {
} }
func (s *Store) IsNew() bool { func (s *Store) IsNew() bool {
return len(s.keys) == 0 return len(s.Keys) == 0
} }
func (s *Store) Unlocked() bool { func (s *Store) Unlocked() bool {
return s.unlocked return s.unlocked
} }
func (s *Store) Init(passphrase []byte) (err error) { func (s *Store) Init(name string, passphrase []byte) (err error) {
err = randRead(s.key[:]) err = randRead(s.key[:])
if err != nil { if err != nil {
return return
} }
err = randRead(s.salt[:]) err = randRead(s.Salt[:])
if err != nil { if err != nil {
return return
} }
s.AddKey(passphrase) s.AddKey(name, passphrase)
s.unlocked = true s.unlocked = true
return return
} }
var jsonFormatHdr = []byte("{json}")
func (s *Store) ReadFrom(in io.Reader) (n int64, err error) { func (s *Store) ReadFrom(in io.Reader) (n int64, err error) {
memzero(s.key[:]) memzero(s.key[:])
s.unlocked = false s.unlocked = false
@ -117,69 +124,77 @@ func (s *Store) ReadFrom(in io.Reader) (n int64, err error) {
n += int64(nr) n += int64(nr)
} }
// read the salt // read the file's start (json header or start of salt)
readFull(s.salt[:])
readFull(s.Salt[:len(jsonFormatHdr)])
if err != nil {
return
}
if !bytes.Equal(s.Salt[:len(jsonFormatHdr)], jsonFormatHdr) {
// old key file
// finish reading the salt
readFull(s.Salt[len(jsonFormatHdr):])
if err != nil { if err != nil {
return return
} }
// read the (encrypted) keys // read the (encrypted) keys
s.keys = make([]keyEntry, 0) s.Keys = make([]KeyEntry, 0)
for { for {
k := keyEntry{} k := KeyEntry{Name: "key-" + strconv.Itoa(len(s.Keys))}
readFull(k.hash[:]) readFull(k.Hash[:])
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
err = nil err = nil
} }
return return
} }
readFull(k.encKey[:]) readFull(k.EncKey[:])
if err != nil { if err != nil {
return return
} }
s.keys = append(s.keys, k) s.Keys = append(s.Keys, k)
} }
} }
err = json.NewDecoder(in).Decode(s)
return
}
func (s *Store) WriteTo(out io.Writer) (n int64, err error) { func (s *Store) WriteTo(out io.Writer) (n int64, err error) {
write := func(ba []byte) { _, err = out.Write(jsonFormatHdr)
var nr int
nr, err = out.Write(ba)
n += int64(nr)
}
write(s.salt[:])
if err != nil { if err != nil {
return return
} }
err = json.NewEncoder(out).Encode(s)
for _, k := range s.keys {
write(k.hash[:])
if err != nil {
return
}
write(k.encKey[:])
if err != nil {
return
}
}
return return
} }
var ErrNoSuchKey = errors.New("no such key") var ErrNoSuchKey = errors.New("no such key")
func (s *Store) HasKey(passphrase []byte) bool {
key, hash := s.keyPairFromPassword(passphrase)
defer memzero(key[:])
for _, k := range s.Keys {
if k.Hash == hash {
return true
}
}
return false
}
func (s *Store) Unlock(passphrase []byte) (ok bool) { func (s *Store) Unlock(passphrase []byte) (ok bool) {
key, hash := s.keyPairFromPassword(passphrase) key, hash := s.keyPairFromPassword(passphrase)
memzero(passphrase)
defer memzero(key[:]) defer memzero(key[:])
var idx = -1 var idx = -1
for i := range s.keys { for i := range s.Keys {
if hash == s.keys[i].hash { if hash == s.Keys[i].Hash {
idx = i idx = i
break break
} }
@ -189,28 +204,28 @@ func (s *Store) Unlock(passphrase []byte) (ok bool) {
return return
} }
s.decryptTo(s.key[:], s.keys[idx].encKey[:], &key) s.decryptTo(s.key[:], s.Keys[idx].EncKey[:], &key)
s.unlocked = true s.unlocked = true
return true return true
} }
func (s *Store) AddKey(passphrase []byte) { func (s *Store) AddKey(name string, passphrase []byte) {
key, hash := s.keyPairFromPassword(passphrase) key, hash := s.keyPairFromPassword(passphrase)
memzero(passphrase) memzero(passphrase)
defer memzero(key[:]) defer memzero(key[:])
k := keyEntry{hash: hash} k := KeyEntry{Name: name, Hash: hash}
encKey := s.encrypt(s.key[:], &key) encKey := s.encrypt(s.key[:], &key)
copy(k.encKey[:], encKey) copy(k.EncKey[:], encKey)
s.keys = append(s.keys, k) s.Keys = append(s.Keys, k)
} }
func (s *Store) keyPairFromPassword(password []byte) (key [32]byte, hash [64]byte) { func (s *Store) keyPairFromPassword(password []byte) (key [32]byte, hash [64]byte) {
keySlice := argon2.IDKey(password, s.salt[:], 1, 64*1024, 4, 32) keySlice := argon2.IDKey(password, s.Salt[:], 1, 64*1024, 4, 32)
copy(key[:], keySlice) copy(key[:], keySlice)
memzero(keySlice) memzero(keySlice)
@ -236,12 +251,12 @@ func (s *Store) NewDecrypter(iv [aes.BlockSize]byte) cipher.Stream {
func (s *Store) encrypt(src []byte, key *[32]byte) (dst []byte) { func (s *Store) encrypt(src []byte, key *[32]byte) (dst []byte) {
dst = make([]byte, len(src)) dst = make([]byte, len(src))
newEncrypter(s.salt, key).XORKeyStream(dst, src) newEncrypter(s.Salt, key).XORKeyStream(dst, src)
return return
} }
func (s *Store) decryptTo(dst []byte, src []byte, key *[32]byte) { func (s *Store) decryptTo(dst []byte, src []byte, key *[32]byte) {
newDecrypter(s.salt, key).XORKeyStream(dst, src) newDecrypter(s.Salt, key).XORKeyStream(dst, src)
} }
func newEncrypter(iv [aes.BlockSize]byte, key *[32]byte) cipher.Stream { func newEncrypter(iv [aes.BlockSize]byte, key *[32]byte) cipher.Stream {

View File

@ -1,27 +0,0 @@
set -e
dir=/var/lib/direktil/
PATH=$PATH:$dir
cd $dir
if [ ! -f govc.env ]; then
echo ERROR: govc.env file not found in dir $dir ; exit 1
fi
source govc.env
if [ $# != 2 ]; then
echo "Usage: $0 <VM_NAME> <NOVIT_HOST>" ; exit 1
fi
if [[ -z $NOVIT_VM_FOLDER || -z $NOVIT_ISO_FOLDER ]]; then
echo "ERROR: All GOVC env vars (including NOVIT_VM_FOLDER and NOVIT_ISO_FOLDER) must be provided" ; exit 1
fi
VM=$1
HOST=$2
govc vm.power -off "$NOVIT_VM_FOLDER/$VM" || true
sleep 5
curl localhost:7606/hosts/$HOST/boot.iso | govc datastore.upload - "$NOVIT_ISO_FOLDER/$VM.iso"
sleep 5
govc vm.power -on "$NOVIT_VM_FOLDER/$VM"