move ui to using trunk
cargo install trunk
This commit is contained in:
@ -1,44 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
restful "github.com/emicklei/go-restful"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type SSH_ACL struct {
|
||||
Keys []string
|
||||
Clusters []string
|
||||
Groups []string
|
||||
Hosts []string
|
||||
}
|
||||
|
||||
func loadSSH_ACLs() (acls []SSH_ACL, err error) {
|
||||
f, err := os.Open(filepath.Join(*dataDir, "ssh-acls.yaml"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
err = yaml.NewDecoder(f).Decode(&acls)
|
||||
return
|
||||
}
|
||||
|
||||
func wsSSH_ACL_List(req *restful.Request, resp *restful.Response) {
|
||||
// TODO
|
||||
http.NotFound(resp.ResponseWriter, req.Request)
|
||||
}
|
||||
|
||||
func wsSSH_ACL_Get(req *restful.Request, resp *restful.Response) {
|
||||
// TODO
|
||||
http.NotFound(resp.ResponseWriter, req.Request)
|
||||
}
|
||||
|
||||
func wsSSH_ACL_Set(req *restful.Request, resp *restful.Response) {
|
||||
// TODO
|
||||
http.NotFound(resp.ResponseWriter, req.Request)
|
||||
}
|
||||
@ -4,9 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
cfsslconfig "github.com/cloudflare/cfssl/config"
|
||||
@ -152,10 +150,6 @@ func registerWS(rest *restful.Container) {
|
||||
ws.Route(ws.GET("/hosts").To(wsListHosts).
|
||||
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
|
||||
@ -176,19 +170,6 @@ func registerWS(rest *restful.Container) {
|
||||
|
||||
rest.Add(ws)
|
||||
|
||||
// Detected host API
|
||||
ws = (&restful.WebService{}).
|
||||
Filter(requireSecStore).
|
||||
Path("/me").
|
||||
Param(ws.HeaderParameter("Authorization", "Host or admin bearer token"))
|
||||
|
||||
(&wsHost{
|
||||
hostDoc: "detected host",
|
||||
getHost: detectHost,
|
||||
}).register(ws, func(rb *restful.RouteBuilder) {
|
||||
rb.Notes("In this case, the host is detected from the remote IP")
|
||||
})
|
||||
|
||||
// Hosts by token API
|
||||
ws = (&restful.WebService{}).
|
||||
Filter(requireSecStore).
|
||||
@ -229,41 +210,6 @@ func requireSecStore(req *restful.Request, resp *restful.Response, chain *restfu
|
||||
chain.ProcessFilter(req, resp)
|
||||
}
|
||||
|
||||
func detectHost(req *restful.Request) (hostName string, err error) {
|
||||
if !*allowDetectedHost {
|
||||
return
|
||||
}
|
||||
|
||||
r := req.Request
|
||||
remoteAddr := r.RemoteAddr
|
||||
|
||||
if *trustXFF {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
remoteAddr = strings.Split(xff, ",")[0]
|
||||
}
|
||||
}
|
||||
|
||||
hostIP, _, err := net.SplitHostPort(remoteAddr)
|
||||
|
||||
if err != nil {
|
||||
hostIP = remoteAddr
|
||||
}
|
||||
|
||||
cfg, err := readConfig()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
host := cfg.HostByIP(hostIP)
|
||||
|
||||
if host == nil {
|
||||
log.Print("no host found for IP ", hostIP)
|
||||
return
|
||||
}
|
||||
|
||||
return host.Name, nil
|
||||
}
|
||||
|
||||
func wsReadConfig(resp *restful.Response) *localconfig.Config {
|
||||
cfg, err := readConfig()
|
||||
if err != nil {
|
||||
|
||||
@ -2,5 +2,5 @@ package dlshtml
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed favicon.ico ui
|
||||
//go:embed ui
|
||||
var FS embed.FS
|
||||
|
||||
24
ui/Trunk.toml
Normal file
24
ui/Trunk.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[build]
|
||||
public_url = "/ui"
|
||||
dist = "../html/ui"
|
||||
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/public-state"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/state"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/public"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/store"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/authorize-download"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/sign-download-set"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/configs"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/clusters"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/hosts-from-template"
|
||||
[[proxy]]
|
||||
backend = "http://localhost:7606/hosts"
|
||||
@ -27,3 +27,16 @@
|
||||
color: var(--link);
|
||||
}
|
||||
}
|
||||
|
||||
.text-and-file {
|
||||
position:relative;
|
||||
|
||||
textarea {
|
||||
width: 64em;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
position:absolute;
|
||||
bottom:0;right:0;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
@ -2,18 +2,24 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Direktil Local Server</title>
|
||||
<style>
|
||||
@import url('./style.css');
|
||||
@import url('./app.css');
|
||||
</style>
|
||||
<script src="js/jsonpatch.min.js" crossorigin="anonymous"></script>
|
||||
<script src="js/app.js" type="module" defer></script>
|
||||
<base data-trunk-public-url />
|
||||
<link data-trunk rel="copy-file" href="favicon.ico" />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<link data-trunk rel="copy-file" href="js/vue.esm-browser.js" />
|
||||
<link data-trunk rel="inline" href="style.css" />
|
||||
<link data-trunk rel="inline" href="app.css" />
|
||||
<script data-trunk src="js/jsonpatch.min.js"></script>
|
||||
<script data-trunk src="js/app.js" type="module" defer></script>
|
||||
<script data-trunk src="js/Downloads.js"></script>
|
||||
<script data-trunk src="js/GetCopy.js"></script>
|
||||
<script data-trunk src="js/Cluster.js"></script>
|
||||
<script data-trunk src="js/Host.js"></script>
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
<header>
|
||||
<div id="logo">
|
||||
<img src="/favicon.ico" />
|
||||
<img src="favicon.ico" />
|
||||
<span>Direktil Local Server</span>
|
||||
</div>
|
||||
<div class="utils">
|
||||
@ -134,6 +140,5 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,8 +1,4 @@
|
||||
|
||||
import Downloads from './Downloads.js';
|
||||
import GetCopy from './GetCopy.js';
|
||||
|
||||
export default {
|
||||
const Cluster = {
|
||||
components: { Downloads, GetCopy },
|
||||
props: [ 'cluster', 'token', 'state' ],
|
||||
data() {
|
||||
@ -52,8 +48,61 @@ export default {
|
||||
})
|
||||
.catch((e) => { alert('failed to sign: '+e); })
|
||||
},
|
||||
readFile(e, onload) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) { return; }
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => { onload(reader.result) };
|
||||
reader.onerror = () => { alert("error reading file"); };
|
||||
reader.readAsText(file);
|
||||
},
|
||||
loadPubKey(e) {
|
||||
this.readFile(e, (v) => {
|
||||
this.sshSignReq.PubKey = v;
|
||||
});
|
||||
},
|
||||
loadCSR(e) {
|
||||
this.readFile(e, (v) => {
|
||||
this.kubeSignReq.CSR = v;
|
||||
});
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<h3>Access</h3>
|
||||
|
||||
<p>Allow cluster access from a public key</p>
|
||||
|
||||
<h4>Grant SSH access</h4>
|
||||
|
||||
<p>Validity: <input type="text" v-model="signReqValidity"/> <small>time range, ie: -5m:1w, 5m, 1M, 1y, 1d-1s, etc.</small></p>
|
||||
<p>User: <input type="text" v-model="sshSignReq.Principal"/></p>
|
||||
<p>Public key (OpenSSH format):<br/>
|
||||
<span class="text-and-file"><textarea v-model="sshSignReq.PubKey" style="height:3lh"></textarea>
|
||||
<input type="file" accept=".pub" @change="loadPubKey" /></span>
|
||||
</p>
|
||||
|
||||
<p><button @click="sshCASign">Sign SSH access</button>
|
||||
<template v-if="sshUserCert">
|
||||
=> <a :href="sshUserCert" download="ssh-cert.pub">Get certificate</a>
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<h4>Grant Kubernetes API access</h4>
|
||||
|
||||
<p>Validity: <input type="text" v-model="signReqValidity"/> <small>time range, ie: -5m:1w, 5m, 1M, 1y, 1d-1s, etc.</small></p>
|
||||
<p>User: <input type="text" v-model="kubeSignReq.User"/> (by default, from the CSR)</p>
|
||||
<p>Group: <input type="text" v-model="kubeSignReq.Group"/></p>
|
||||
<p>Certificate signing request (PEM format):<br/>
|
||||
<span class="text-and-file"><textarea v-model="kubeSignReq.CSR" style="height:7lh;"></textarea>
|
||||
<input type="file" accept=".csr" @change="loadCSR" /></span>
|
||||
</p>
|
||||
|
||||
<p><button @click="kubeCASign">Sign Kubernetes API access</button>
|
||||
<template v-if="kubeUserCert">
|
||||
=> <a :href="kubeUserCert" download="kube-cert.pub">Get certificate</a>
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<h3>Tokens</h3>
|
||||
<section class="links">
|
||||
<GetCopy v-for="n in cluster.Tokens" :token="token" :name="n" :href="'/clusters/'+cluster.Name+'/tokens/'+n" />
|
||||
@ -78,36 +127,5 @@ export default {
|
||||
</template></td>
|
||||
</tr></table>
|
||||
|
||||
<h3>Access</h3>
|
||||
|
||||
<p>Allow cluster access from a public key</p>
|
||||
<p>Certificate time validity: <input type="text" v-model="signReqValidity"/> <small>ie: -5m:1w, 5m, 1M, 1y, 1d-1s, etc.</p>
|
||||
|
||||
<h4>Grant SSH access</h4>
|
||||
|
||||
<p>Public key (OpenSSH format):<br/>
|
||||
<textarea v-model="sshSignReq.PubKey" style="width:64em;height:2lh"></textarea>
|
||||
</p>
|
||||
<p>User: <input type="text" v-model="sshSignReq.Principal"/></p>
|
||||
|
||||
<p><button @click="sshCASign">Sign SSH access (validity: {{signReqValidity}})</button>
|
||||
<template v-if="sshUserCert">
|
||||
=> <a :href="sshUserCert" download="ssh-cert.pub">Get certificate</a>
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<h4>Grant Kubernetes API access</h4>
|
||||
|
||||
<p>Certificate signing request (PEM format):<br/>
|
||||
<textarea v-model="kubeSignReq.CSR" style="width:64em;height:7lh;"></textarea>
|
||||
</p>
|
||||
<p>User: <input type="text" v-model="kubeSignReq.User"/></p>
|
||||
<p>Group: <input type="text" v-model="kubeSignReq.Group"/></p>
|
||||
|
||||
<p><button @click="kubeCASign">Sign Kubernetes API access (validity: {{signReqValidity}})</button>
|
||||
<template v-if="kubeUserCert">
|
||||
=> <a :href="kubeUserCert" download="kube-cert.pub">Get certificate</a>
|
||||
</template>
|
||||
</p>
|
||||
`
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
|
||||
export default {
|
||||
const Downloads = {
|
||||
props: [ 'kind', 'name', 'token', 'state' ],
|
||||
data() {
|
||||
return { createDisabled: false, selectedAssets: {} }
|
||||
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
const GetCopy = {
|
||||
props: [ 'name', 'href', 'token' ],
|
||||
data() { return {showCopied: false} },
|
||||
template: `<span class="notif"><div v-if="showCopied">copied!</div><a :href="href" @click="fetchAndSave()">{{name}}</a> <a href="#" class="copy" @click="fetchAndCopy()">🗐</a></span>`,
|
||||
@ -1,7 +1,4 @@
|
||||
|
||||
import Downloads from './Downloads.js';
|
||||
|
||||
export default {
|
||||
const Host = {
|
||||
components: { Downloads },
|
||||
props: [ 'host', 'token', 'state' ],
|
||||
template: `
|
||||
@ -1,9 +1,6 @@
|
||||
|
||||
import { createApp } from './vue.esm-browser.js';
|
||||
|
||||
import Cluster from './Cluster.js';
|
||||
import Host from './Host.js';
|
||||
|
||||
createApp({
|
||||
components: { Cluster, Host },
|
||||
data() {
|
||||
@ -262,5 +259,4 @@ createApp({
|
||||
},
|
||||
}
|
||||
|
||||
}).mount('#app')
|
||||
|
||||
}).mount('#app');
|
||||
Reference in New Issue
Block a user