Compare commits
No commits in common. "3bc20e95cc1b22dbc64e47f88b4d30ab18c26817" and "5c432e3b42a9f25134fc3af344d34b86fa2799ab" have entirely different histories.
3bc20e95cc
...
5c432e3b42
@ -1,5 +1,5 @@
|
|||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
from mcluseau/golang-builder:1.20.0 as build
|
from mcluseau/golang-builder:1.19.4 as build
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
from debian:stretch
|
from debian:stretch
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"m.cluseau.fr/go/httperr"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrNotFound = httperr.NewStd(404, http.StatusNotFound, "not found")
|
|
@ -8,13 +8,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
"m.cluseau.fr/go/httperr"
|
"m.cluseau.fr/go/httperr"
|
||||||
|
|
||||||
"novit.tech/direktil/local-server/secretstore"
|
"novit.tech/direktil/local-server/secretstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -130,7 +126,6 @@ func unlockSecretStore(passphrase []byte) *httperr.Error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
go updateState()
|
go updateState()
|
||||||
go migrateSecrets()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -152,13 +147,7 @@ func readSecret(name string, value any) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeSecret(name string, value any) (err error) {
|
func writeSecret(name string, value any) (err error) {
|
||||||
path := secStorePath(name + ".data.new")
|
f, err := os.Create(secStorePath(name + ".data.new"))
|
||||||
|
|
||||||
if err = os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -178,148 +167,5 @@ func writeSecret(name string, value any) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Rename(f.Name(), secStorePath(name+".data"))
|
return os.Rename(f.Name(), secStorePath(name+".data"))
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go updateState()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var secL sync.Mutex
|
|
||||||
|
|
||||||
func updateSecret[T any](name string, update func(*T)) (err error) {
|
|
||||||
secL.Lock()
|
|
||||||
defer secL.Unlock()
|
|
||||||
|
|
||||||
v := new(T)
|
|
||||||
err = readSecret(name, v)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
update(v)
|
|
||||||
|
|
||||||
return writeSecret(name, *v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateSecretWithKey[T any](name, key string, update func(v *T)) (err error) {
|
|
||||||
secL.Lock()
|
|
||||||
defer secL.Unlock()
|
|
||||||
|
|
||||||
kvs := map[string]*T{}
|
|
||||||
|
|
||||||
err = readSecret(name, &kvs)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
update(kvs[key])
|
|
||||||
|
|
||||||
return writeSecret(name, kvs)
|
|
||||||
}
|
|
||||||
|
|
||||||
type KVSecrets[T any] struct{ Name string }
|
|
||||||
|
|
||||||
func (s KVSecrets[T]) Data() (kvs map[string]T, err error) {
|
|
||||||
kvs = make(map[string]T)
|
|
||||||
err = readSecret(s.Name, &kvs)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s KVSecrets[T]) Keys(prefix string) (keys []string, err error) {
|
|
||||||
kvs, err := s.Data()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keys = make([]string, 0, len(kvs))
|
|
||||||
|
|
||||||
for k := range kvs {
|
|
||||||
if !strings.HasPrefix(k, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keys = append(keys, k[len(prefix):])
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s KVSecrets[T]) Get(key string) (v T, found bool, err error) {
|
|
||||||
kvs, err := s.Data()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
v, found = kvs[key]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s KVSecrets[T]) Put(key string, v T) (err error) {
|
|
||||||
secL.Lock()
|
|
||||||
defer secL.Unlock()
|
|
||||||
|
|
||||||
kvs, err := s.Data()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
kvs[key] = v
|
|
||||||
err = writeSecret(s.Name, kvs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s KVSecrets[T]) WsList(resp *restful.Response, prefix string) {
|
|
||||||
keys, err := s.Keys(prefix)
|
|
||||||
if err != nil {
|
|
||||||
httperr.New(http.StatusInternalServerError, err).WriteJSON(resp.ResponseWriter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s KVSecrets[T]) WsGet(resp *restful.Response, key string) {
|
|
||||||
keys, found, err := s.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
httperr.New(http.StatusInternalServerError, err).WriteJSON(resp.ResponseWriter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
ErrNotFound.WriteJSON(resp.ResponseWriter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s KVSecrets[T]) WsPut(req *restful.Request, resp *restful.Response, key string) {
|
|
||||||
v := new(T)
|
|
||||||
err := req.ReadEntity(v)
|
|
||||||
if err != nil {
|
|
||||||
httperr.New(http.StatusBadRequest, err).WriteJSON(resp.ResponseWriter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.Put(key, *v)
|
|
||||||
if err != nil {
|
|
||||||
httperr.New(http.StatusInternalServerError, err).WriteJSON(resp.ResponseWriter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
cfsslconfig "github.com/cloudflare/cfssl/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func migrateSecrets() {
|
|
||||||
if _, err := os.Stat(secretDataPath()); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Print("not migrating old secrets: ", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Print("migrating old secrets")
|
|
||||||
|
|
||||||
log := log.New(log.Default().Writer(), "secrets migration: ", log.Flags()|log.Lmsgprefix)
|
|
||||||
|
|
||||||
// load secrets
|
|
||||||
cfg, err := readConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var sslCfg *cfsslconfig.Config
|
|
||||||
|
|
||||||
if len(cfg.SSLConfig) == 0 {
|
|
||||||
sslCfg = &cfsslconfig.Config{}
|
|
||||||
} else {
|
|
||||||
sslCfg, err = cfsslconfig.LoadConfig([]byte(cfg.SSLConfig))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := loadSecretData(sslCfg); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for clusterName, cluster := range secretData.clusters {
|
|
||||||
for k, v := range cluster.Tokens {
|
|
||||||
err = clusterTokens.Put(clusterName+"/"+k, v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range cluster.Passwords {
|
|
||||||
err = clusterPasswords.Put(clusterName+"/"+k, v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for caName, ca := range cluster.CAs {
|
|
||||||
clusterCAs.Put(clusterName+"/"+caName, CA{Key: ca.Key, Cert: ca.Cert})
|
|
||||||
|
|
||||||
for signedName, signed := range ca.Signed {
|
|
||||||
clusterCASignedKeys.Put(clusterName+"/"+caName+"/"+signedName, *signed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
@ -28,11 +28,11 @@ type State struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ClusterState struct {
|
type ClusterState struct {
|
||||||
Name string
|
Name string
|
||||||
Addons bool
|
Addons bool
|
||||||
Passwords []string
|
// TODO CAs
|
||||||
Tokens []string
|
// TODO passwords
|
||||||
CAs []CAState
|
// TODO tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
type HostState struct {
|
type HostState struct {
|
||||||
@ -41,11 +41,6 @@ type HostState struct {
|
|||||||
IPs []string
|
IPs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CAState struct {
|
|
||||||
Name string
|
|
||||||
Signed []string
|
|
||||||
}
|
|
||||||
|
|
||||||
var wState = watchable.New[State]()
|
var wState = watchable.New[State]()
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -73,34 +68,6 @@ func updateState() {
|
|||||||
Name: cluster.Name,
|
Name: cluster.Name,
|
||||||
Addons: len(cluster.Addons) != 0,
|
Addons: len(cluster.Addons) != 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Passwords, err = clusterPasswords.Keys(c.Name + "/")
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to read cluster passwords: ", err)
|
|
||||||
}
|
|
||||||
c.Tokens, err = clusterTokens.Keys(c.Name + "/")
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to read cluster tokens: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
caNames, err := clusterCAs.Keys(c.Name + "/")
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to read cluster CAs: ", err)
|
|
||||||
}
|
|
||||||
for _, caName := range caNames {
|
|
||||||
ca := CAState{Name: caName}
|
|
||||||
|
|
||||||
signedNames, err := clusterCASignedKeys.Keys(c.Name + "/" + caName + "/")
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to read cluster CA signed keys: ", err)
|
|
||||||
}
|
|
||||||
for _, signedName := range signedNames {
|
|
||||||
ca.Signed = append(ca.Signed, signedName)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.CAs = append(c.CAs, ca)
|
|
||||||
}
|
|
||||||
|
|
||||||
clusters = append(clusters, c)
|
clusters = append(clusters, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
var clusterCAs = newClusterSecretKV[CA]("CAs")
|
|
||||||
|
|
||||||
func wsClusterCAs(req *restful.Request, resp *restful.Response) {
|
|
||||||
clusterName := req.PathParameter("cluster-name")
|
|
||||||
clusterCAs.WsList(resp, clusterName+"/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func wsClusterCA(req *restful.Request, resp *restful.Response) {
|
|
||||||
clusterName := req.PathParameter("cluster-name")
|
|
||||||
name := req.PathParameter("ca-name")
|
|
||||||
|
|
||||||
clusterCAs.WsGet(resp, clusterName+"/"+name)
|
|
||||||
}
|
|
||||||
|
|
||||||
var clusterCASignedKeys = newClusterSecretKV[KeyCert]("CA-signed-keys")
|
|
||||||
|
|
||||||
func wsClusterCASignedKeys(req *restful.Request, resp *restful.Response) {
|
|
||||||
clusterName := req.PathParameter("cluster-name")
|
|
||||||
caName := req.PathParameter("ca-name")
|
|
||||||
clusterCASignedKeys.WsList(resp, clusterName+"/"+caName+"/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func wsClusterCASignedKey(req *restful.Request, resp *restful.Response) {
|
|
||||||
clusterName := req.PathParameter("cluster-name")
|
|
||||||
caName := req.PathParameter("ca-name")
|
|
||||||
name := req.PathParameter("signed-name")
|
|
||||||
|
|
||||||
clusterCASignedKeys.WsGet(resp, clusterName+"/"+caName+"/"+name)
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
var clusterPasswords = newClusterSecretKV[string]("passwords")
|
|
||||||
|
|
||||||
func wsClusterPasswords(req *restful.Request, resp *restful.Response) {
|
|
||||||
clusterName := req.PathParameter("cluster-name")
|
|
||||||
clusterPasswords.WsList(resp, clusterName+"/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func wsClusterPassword(req *restful.Request, resp *restful.Response) {
|
|
||||||
clusterName := req.PathParameter("cluster-name")
|
|
||||||
name := req.PathParameter("password-name")
|
|
||||||
|
|
||||||
clusterPasswords.WsGet(resp, clusterName+"/"+name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func wsClusterSetPassword(req *restful.Request, resp *restful.Response) {
|
|
||||||
cluster := wsReadCluster(req, resp)
|
|
||||||
if cluster == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
name := req.PathParameter("password-name")
|
|
||||||
|
|
||||||
clusterPasswords.WsPut(req, resp, cluster.Name+"/"+name)
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
var clusterTokens = newClusterSecretKV[string]("tokens")
|
|
||||||
|
|
||||||
func wsClusterTokens(req *restful.Request, resp *restful.Response) {
|
|
||||||
clusterName := req.PathParameter("cluster-name")
|
|
||||||
clusterTokens.WsList(resp, clusterName+"/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func wsClusterToken(req *restful.Request, resp *restful.Response) {
|
|
||||||
clusterName := req.PathParameter("cluster-name")
|
|
||||||
name := req.PathParameter("token-name")
|
|
||||||
|
|
||||||
clusterTokens.WsGet(resp, clusterName+"/"+name)
|
|
||||||
}
|
|
@ -9,13 +9,6 @@ import (
|
|||||||
"novit.tech/direktil/pkg/localconfig"
|
"novit.tech/direktil/pkg/localconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
var clusterSecretKVs = []string{}
|
|
||||||
|
|
||||||
func newClusterSecretKV[T any](name string) KVSecrets[T] {
|
|
||||||
clusterSecretKVs = append(clusterSecretKVs, name)
|
|
||||||
return KVSecrets[T]{"clusters/"+name}
|
|
||||||
}
|
|
||||||
|
|
||||||
func wsListClusters(req *restful.Request, resp *restful.Response) {
|
func wsListClusters(req *restful.Request, resp *restful.Response) {
|
||||||
cfg := wsReadConfig(resp)
|
cfg := wsReadConfig(resp)
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
@ -71,6 +64,97 @@ func wsClusterAddons(req *restful.Request, resp *restful.Response) {
|
|||||||
wsRender(resp, cluster.Addons, cluster)
|
wsRender(resp, cluster.Addons, cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wsClusterPasswords(req *restful.Request, resp *restful.Response) {
|
||||||
|
cluster := wsReadCluster(req, resp)
|
||||||
|
if cluster == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.WriteEntity(secretData.Passwords(cluster.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsClusterPassword(req *restful.Request, resp *restful.Response) {
|
||||||
|
cluster := wsReadCluster(req, resp)
|
||||||
|
if cluster == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.PathParameter("password-name")
|
||||||
|
|
||||||
|
resp.WriteEntity(secretData.Password(cluster.Name, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsClusterSetPassword(req *restful.Request, resp *restful.Response) {
|
||||||
|
cluster := wsReadCluster(req, resp)
|
||||||
|
if cluster == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.PathParameter("password-name")
|
||||||
|
|
||||||
|
var password string
|
||||||
|
if err := req.ReadEntity(&password); err != nil {
|
||||||
|
wsError(resp, err) // FIXME this is a BadRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secretData.SetPassword(cluster.Name, name, password)
|
||||||
|
|
||||||
|
if err := secretData.Save(); err != nil {
|
||||||
|
wsError(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsClusterToken(req *restful.Request, resp *restful.Response) {
|
||||||
|
cluster := wsReadCluster(req, resp)
|
||||||
|
if cluster == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.PathParameter("token-name")
|
||||||
|
|
||||||
|
token, err := secretData.Token(cluster.Name, name)
|
||||||
|
if err != nil {
|
||||||
|
wsError(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.WriteEntity(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsClusterBootstrapPods(req *restful.Request, resp *restful.Response) {
|
||||||
|
cluster := wsReadCluster(req, resp)
|
||||||
|
if cluster == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cluster.BootstrapPods) == 0 {
|
||||||
|
log.Printf("cluster %q has no bootstrap pods defined", cluster.Name)
|
||||||
|
wsNotFound(req, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wsRender(resp, cluster.BootstrapPods, cluster)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsClusterCAs(req *restful.Request, resp *restful.Response) {
|
||||||
|
cs := secretData.clusters[req.PathParameter("cluster-name")]
|
||||||
|
if cs == nil {
|
||||||
|
wsNotFound(req, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(cs.CAs))
|
||||||
|
for k := range cs.CAs {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
resp.WriteJson(keys, restful.MIME_JSON)
|
||||||
|
}
|
||||||
|
|
||||||
func wsClusterCACert(req *restful.Request, resp *restful.Response) {
|
func wsClusterCACert(req *restful.Request, resp *restful.Response) {
|
||||||
cs := secretData.clusters[req.PathParameter("cluster-name")]
|
cs := secretData.clusters[req.PathParameter("cluster-name")]
|
||||||
if cs == nil {
|
if cs == nil {
|
||||||
|
@ -53,50 +53,40 @@ 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"))
|
||||||
|
|
||||||
const (
|
ws.Route(ws.GET("/clusters/{cluster-name}").To(wsCluster).
|
||||||
GET = http.MethodGet
|
Doc("Get cluster details"))
|
||||||
PUT = http.MethodPut
|
|
||||||
)
|
|
||||||
|
|
||||||
cluster := func(method, subPath string) *restful.RouteBuilder {
|
ws.Route(ws.GET("/clusters/{cluster-name}/addons").To(wsClusterAddons).
|
||||||
return ws.Method(method).Path("/clusters/{cluster-name}" + subPath).
|
Produces(mime.YAML).
|
||||||
Param(ws.PathParameter("cluster-name", "name of the cluster"))
|
Doc("Get cluster addons").
|
||||||
}
|
Returns(http.StatusOK, "OK", nil).
|
||||||
|
Returns(http.StatusNotFound, "The cluster does not exists or does not have addons defined", nil))
|
||||||
|
|
||||||
for _, builder := range []*restful.RouteBuilder{
|
ws.Route(ws.GET("/clusters/{cluster-name}/bootstrap-pods").To(wsClusterBootstrapPods).
|
||||||
cluster(GET, "").To(wsCluster).
|
Produces(mime.YAML).
|
||||||
Doc("Get cluster details"),
|
Doc("Get cluster bootstrap pods YAML definitions").
|
||||||
|
Returns(http.StatusOK, "OK", nil).
|
||||||
|
Returns(http.StatusNotFound, "The cluster does not exists or does not have bootstrap pods defined", nil))
|
||||||
|
|
||||||
cluster(GET, "/addons").To(wsClusterAddons).
|
ws.Route(ws.GET("/clusters/{cluster-name}/passwords").To(wsClusterPasswords).
|
||||||
Produces(mime.YAML).
|
Doc("List cluster's passwords"))
|
||||||
Doc("Get cluster addons").
|
ws.Route(ws.GET("/clusters/{cluster-name}/passwords/{password-name}").To(wsClusterPassword).
|
||||||
Returns(http.StatusOK, "OK", nil).
|
Doc("Get cluster's password"))
|
||||||
Returns(http.StatusNotFound, "The cluster does not exists or does not have addons defined", nil),
|
ws.Route(ws.PUT("/clusters/{cluster-name}/passwords/{password-name}").To(wsClusterSetPassword).
|
||||||
|
Doc("Set cluster's password"))
|
||||||
|
|
||||||
cluster(GET, "/tokens").To(wsClusterTokens).
|
ws.Route(ws.GET("/clusters/{cluster-name}/ca").To(wsClusterCAs).
|
||||||
Doc("List cluster's tokens"),
|
Doc("Get cluster CAs"))
|
||||||
cluster(GET, "/tokens/{token-name}").To(wsClusterToken).
|
ws.Route(ws.GET("/clusters/{cluster-name}/ca/{ca-name}/certificate").To(wsClusterCACert).
|
||||||
Doc("Get cluster's token"),
|
Produces(mime.CACERT).
|
||||||
|
Doc("Get cluster CA's certificate"))
|
||||||
|
ws.Route(ws.GET("/clusters/{cluster-name}/ca/{ca-name}/signed").To(wsClusterSignedCert).
|
||||||
|
Produces(mime.CERT).
|
||||||
|
Param(ws.QueryParameter("name", "signed reference name").Required(true)).
|
||||||
|
Doc("Get cluster's certificate signed by the CA"))
|
||||||
|
|
||||||
cluster(GET, "/passwords").To(wsClusterPasswords).
|
ws.Route(ws.GET("/clusters/{cluster-name}/tokens/{token-name}").To(wsClusterToken).
|
||||||
Doc("List cluster's passwords"),
|
Doc("Get cluster's token"))
|
||||||
cluster(GET, "/passwords/{password-name}").To(wsClusterPassword).
|
|
||||||
Doc("Get cluster's password"),
|
|
||||||
cluster(PUT, "/passwords/{password-name}").To(wsClusterSetPassword).
|
|
||||||
Doc("Set cluster's password"),
|
|
||||||
|
|
||||||
cluster(GET, "/CAs").To(wsClusterCAs).
|
|
||||||
Doc("Get cluster CAs"),
|
|
||||||
cluster(GET, "/CAs/{ca-name}/certificate").To(wsClusterCACert).
|
|
||||||
Produces(mime.CACERT).
|
|
||||||
Doc("Get cluster CA's certificate"),
|
|
||||||
cluster(GET, "/CAs/{ca-name}/signed").To(wsClusterSignedCert).
|
|
||||||
Produces(mime.CERT).
|
|
||||||
Param(ws.QueryParameter("name", "signed reference name").Required(true)).
|
|
||||||
Doc("Get cluster's certificate signed by the CA"),
|
|
||||||
} {
|
|
||||||
ws.Route(builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/hosts").To(wsListHosts).
|
ws.Route(ws.GET("/hosts").To(wsListHosts).
|
||||||
Doc("List hosts"))
|
Doc("List hosts"))
|
||||||
|
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
|||||||
module novit.tech/direktil/local-server
|
module novit.tech/direktil/local-server
|
||||||
|
|
||||||
go 1.20
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cavaliergopher/cpio v1.0.1
|
github.com/cavaliergopher/cpio v1.0.1
|
||||||
|
@ -1,37 +1,16 @@
|
|||||||
|
|
||||||
import Downloads from './Downloads.js';
|
import Downloads from './Downloads.js';
|
||||||
import GetCopy from './GetCopy.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { Downloads, GetCopy },
|
components: { Downloads },
|
||||||
props: [ 'cluster', 'token', 'state' ],
|
props: [ 'cluster', 'token', 'state' ],
|
||||||
template: `
|
template: `
|
||||||
<div class="cluster">
|
<div class="cluster">
|
||||||
<div class="title">Cluster {{ cluster.Name }}</div>
|
<div class="title">Cluster {{ cluster.Name }}</div>
|
||||||
<div class="section">Tokens</div>
|
|
||||||
<section class="links">
|
|
||||||
<GetCopy v-for="n in cluster.Tokens" :token="token" :name="n" :href="'/clusters/'+cluster.Name+'/tokens/'+n" />
|
|
||||||
</section>
|
|
||||||
<div class="section">Passwords</div>
|
|
||||||
<section class="links">
|
|
||||||
<GetCopy v-for="n in cluster.Passwords" :token="token" :name="n" :href="'/clusters/'+cluster.Name+'/passwords/'+n" />
|
|
||||||
</section>
|
|
||||||
<div class="section">Downloads</div>
|
<div class="section">Downloads</div>
|
||||||
<section class="downloads">
|
<section class="downloads">
|
||||||
<Downloads :token="token" :state="state" kind="cluster" :name="cluster.Name" />
|
<Downloads :token="token" :state="state" kind="cluster" :name="cluster.Name" />
|
||||||
</section>
|
</section>
|
||||||
<div class="section">CAs</div>
|
|
||||||
<section v-for="ca in cluster.CAs">
|
|
||||||
{{ ca.Name }}:
|
|
||||||
<GetCopy :token="token" name="cert" :href="'/clusters/'+cluster.Name+'/CAs/'+ca.Name+'/certificate'" />
|
|
||||||
<template v-if="ca.Signed">
|
|
||||||
{{" "}}signed
|
|
||||||
<template v-for="signed in ca.Signed">
|
|
||||||
{{" "}}
|
|
||||||
<GetCopy :token="token" :name="signed" :href="'/clusters/'+cluster.Name+'/CAs/'+ca.Name+'/signed?name='+signed" />
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
export default {
|
|
||||||
props: [ 'name', 'href', 'token' ],
|
|
||||||
data() { return {showCopied: false} },
|
|
||||||
template: `<span class="notif"><div v-if="showCopied">copied!</div><a :href="href" @click="fetchAndCopy()">{{name}}<small> 🗐</small></a></span>`,
|
|
||||||
methods: {
|
|
||||||
fetchAndCopy() {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
fetch(this.href, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Authorization': 'Bearer ' + this.token },
|
|
||||||
}).then((resp) => resp.headers.get("content-type") == "application/json" ? resp.json() : resp.text())
|
|
||||||
.then((value) => {
|
|
||||||
window.navigator.clipboard.writeText(value)
|
|
||||||
this.showCopied = true
|
|
||||||
setTimeout(() => { this.showCopied = false }, 1000)
|
|
||||||
})
|
|
||||||
.catch((e) => { console.log("failed to get value:", e); alert('failed to get value') })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
@ -45,7 +45,7 @@ th, tr:last-child > td {
|
|||||||
background: #333;
|
background: #333;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
}
|
}
|
||||||
a[href], a[href]:visited, button.link {
|
a[href], button.link {
|
||||||
border: none;
|
border: none;
|
||||||
color: #31b0fa;
|
color: #31b0fa;
|
||||||
}
|
}
|
||||||
@ -125,23 +125,3 @@ header .utils > * {
|
|||||||
margin: 2pt 6pt 6pt 6pt;
|
margin: 2pt 6pt 6pt 6pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notif {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.notif > div:first-child {
|
|
||||||
position: absolute;
|
|
||||||
min-width: 100%; height: 100%;
|
|
||||||
background: white;
|
|
||||||
opacity: 75%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.links > * { margin-left: 1ex; }
|
|
||||||
.links > *:first-child { margin-left: 0; }
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.notif > div:first-child {
|
|
||||||
background: black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user