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"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
cfsslconfig "github.com/cloudflare/cfssl/config"
|
cfsslconfig "github.com/cloudflare/cfssl/config"
|
||||||
@ -152,10 +150,6 @@ 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)
|
rest.Add(ws)
|
||||||
|
|
||||||
// Hosts API
|
// Hosts API
|
||||||
@ -176,19 +170,6 @@ func registerWS(rest *restful.Container) {
|
|||||||
|
|
||||||
rest.Add(ws)
|
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
|
// Hosts by token API
|
||||||
ws = (&restful.WebService{}).
|
ws = (&restful.WebService{}).
|
||||||
Filter(requireSecStore).
|
Filter(requireSecStore).
|
||||||
@ -229,41 +210,6 @@ func requireSecStore(req *restful.Request, resp *restful.Response, chain *restfu
|
|||||||
chain.ProcessFilter(req, resp)
|
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 {
|
func wsReadConfig(resp *restful.Response) *localconfig.Config {
|
||||||
cfg, err := readConfig()
|
cfg, err := readConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -2,5 +2,5 @@ package dlshtml
|
|||||||
|
|
||||||
import "embed"
|
import "embed"
|
||||||
|
|
||||||
//go:embed favicon.ico ui
|
//go:embed ui
|
||||||
var FS embed.FS
|
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);
|
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>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Direktil Local Server</title>
|
<title>Direktil Local Server</title>
|
||||||
<style>
|
<base data-trunk-public-url />
|
||||||
@import url('./style.css');
|
<link data-trunk rel="copy-file" href="favicon.ico" />
|
||||||
@import url('./app.css');
|
<link rel="icon" href="favicon.ico" />
|
||||||
</style>
|
<link data-trunk rel="copy-file" href="js/vue.esm-browser.js" />
|
||||||
<script src="js/jsonpatch.min.js" crossorigin="anonymous"></script>
|
<link data-trunk rel="inline" href="style.css" />
|
||||||
<script src="js/app.js" type="module" defer></script>
|
<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>
|
<body>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<header>
|
<header>
|
||||||
<div id="logo">
|
<div id="logo">
|
||||||
<img src="/favicon.ico" />
|
<img src="favicon.ico" />
|
||||||
<span>Direktil Local Server</span>
|
<span>Direktil Local Server</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="utils">
|
<div class="utils">
|
||||||
@ -134,6 +140,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -1,8 +1,4 @@
|
|||||||
|
const Cluster = {
|
||||||
import Downloads from './Downloads.js';
|
|
||||||
import GetCopy from './GetCopy.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { Downloads, GetCopy },
|
components: { Downloads, GetCopy },
|
||||||
props: [ 'cluster', 'token', 'state' ],
|
props: [ 'cluster', 'token', 'state' ],
|
||||||
data() {
|
data() {
|
||||||
@ -52,8 +48,61 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((e) => { alert('failed to sign: '+e); })
|
.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: `
|
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>
|
<h3>Tokens</h3>
|
||||||
<section class="links">
|
<section class="links">
|
||||||
<GetCopy v-for="n in cluster.Tokens" :token="token" :name="n" :href="'/clusters/'+cluster.Name+'/tokens/'+n" />
|
<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>
|
</template></td>
|
||||||
</tr></table>
|
</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 @@
|
|||||||
|
const Downloads = {
|
||||||
export default {
|
|
||||||
props: [ 'kind', 'name', 'token', 'state' ],
|
props: [ 'kind', 'name', 'token', 'state' ],
|
||||||
data() {
|
data() {
|
||||||
return { createDisabled: false, selectedAssets: {} }
|
return { createDisabled: false, selectedAssets: {} }
|
||||||
@ -1,4 +1,4 @@
|
|||||||
export default {
|
const GetCopy = {
|
||||||
props: [ 'name', 'href', 'token' ],
|
props: [ 'name', 'href', 'token' ],
|
||||||
data() { return {showCopied: false} },
|
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>`,
|
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 @@
|
|||||||
|
const Host = {
|
||||||
import Downloads from './Downloads.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { Downloads },
|
components: { Downloads },
|
||||||
props: [ 'host', 'token', 'state' ],
|
props: [ 'host', 'token', 'state' ],
|
||||||
template: `
|
template: `
|
||||||
@ -1,9 +1,6 @@
|
|||||||
|
|
||||||
import { createApp } from './vue.esm-browser.js';
|
import { createApp } from './vue.esm-browser.js';
|
||||||
|
|
||||||
import Cluster from './Cluster.js';
|
|
||||||
import Host from './Host.js';
|
|
||||||
|
|
||||||
createApp({
|
createApp({
|
||||||
components: { Cluster, Host },
|
components: { Cluster, Host },
|
||||||
data() {
|
data() {
|
||||||
@ -262,5 +259,4 @@ createApp({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
}).mount('#app')
|
}).mount('#app');
|
||||||
|
|
||||||
Reference in New Issue
Block a user