signing ui improvements

This commit is contained in:
Mikaël Cluseau
2026-07-20 13:42:48 +02:00
parent d2aefba82e
commit 03f087c5a3
3 changed files with 51 additions and 37 deletions
@@ -8,13 +8,11 @@ const ClusterAccess = {
PubKey: "",
Principal: "root",
},
sshUserCert: null,
kubeSignReq: {
CSR: "",
User: "",
Group: "system:masters",
},
kubeUserCert: null,
downloadSet: null,
selectedAssets: {},
loading: false,
@@ -30,8 +28,8 @@ const ClusterAccess = {
headers: { 'Content-Type': 'application/json' },
}).then(r => {
if (r.ok) r.json().then(d => {
if (d.user) this.kubeSignReq.User = d.user;
if (d.group) this.kubeSignReq.Group = d.group;
this.kubeSignReq.User = d.user || "anonymous";
this.kubeSignReq.Group = d.group;
});
}).catch(() => {});
}
@@ -73,7 +71,15 @@ const ClusterAccess = {
headers: { 'Authorization': 'Bearer ' + this.token, 'Content-Type': 'application/json' },
}).then((resp) => {
if (resp.ok) {
resp.blob().then((cert) => { this.sshUserCert = URL.createObjectURL(cert); this.loading = false })
resp.blob().then((cert) => {
const url = URL.createObjectURL(cert);
const a = document.createElement('a');
const parts = this.sshSignReq.PubKey.trim().split(/\s+/);
const user = parts.length > 2 ? parts[parts.length - 1] : this.sshSignReq.Principal;
a.href = url; a.download = user + '-cert.pub';
a.click(); URL.revokeObjectURL(url);
this.loading = false;
})
} else {
resp.json().then((resp) => { this.msg = 'failed to sign: '+resp.message; this.loading = false })
}
@@ -89,7 +95,13 @@ const ClusterAccess = {
headers: { 'Authorization': 'Bearer ' + this.token, 'Content-Type': 'application/json' },
}).then((resp) => {
if (resp.ok) {
resp.blob().then((cert) => { this.kubeUserCert = URL.createObjectURL(cert); this.loading = false })
resp.blob().then((cert) => {
const url = URL.createObjectURL(cert);
const a = document.createElement('a');
a.href = url; a.download = (this.kubeSignReq.User || this.kubeSignReq.Group) + '.crt';
a.click(); URL.revokeObjectURL(url);
this.loading = false;
})
} else {
resp.json().then((resp) => { this.msg = 'failed to sign: '+resp.message; this.loading = false })
}
@@ -152,7 +164,8 @@ const ClusterAccess = {
<label class="radio"><input type="radio" v-model="accessType" value="download-set" /> Download set</label>
</p>
<p>Validity: <input type="text" v-model="signReqValidity"/> <small>time range, ie: -5m:1w, 5m, 1M, 1y, 1d-1s, etc.</small></p>
<p>Validity: <input type="text" v-model="signReqValidity"/>
<template v-for="(v,i) in ['5m','1d','1w','1M','1y']">{{ i ? ', ' : ' ' }}<a href="#" @click.prevent="signReqValidity=v">{{ v }}</a></template></p>
<p class="error" v-if="msg">{{ msg }} <button class="btn-close" @click="msg=null">&times;</button></p>
@@ -163,26 +176,20 @@ const ClusterAccess = {
<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 :disabled="loading" @click="sshCASign">{{ loading ? 'signing...' : 'Sign SSH access' }}</button>
<template v-if="sshUserCert">
=&gt; <a :href="sshUserCert" download="ssh-cert.pub">Get certificate</a>
</template>
</p>
<p><button :disabled="loading" @click="sshCASign">{{ loading ? 'signing...' : 'Sign SSH access' }}</button></p>
</div>
<div v-if="accessType == 'kube'">
<h4>Grant Kubernetes API access</h4>
<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 :disabled="loading" @click="kubeCASign">{{ loading ? 'signing...' : 'Sign Kubernetes API access' }}</button>
<template v-if="kubeUserCert">
=&gt; <a :href="kubeUserCert" download="tls.crt">Get certificate</a>
</template>
<p>User: <input type="text" v-model="kubeSignReq.User"/></p>
<p>Group: <input type="text" v-model="kubeSignReq.Group"/>
<a href="#" @click.prevent="kubeSignReq.Group=''">clear</a>, <a href="#" @click.prevent="kubeSignReq.Group='system:masters'">system:masters</a>
</p>
<p><button :disabled="loading" @click="kubeCASign">{{ loading ? 'signing...' : 'Sign Kubernetes API access' }}</button></p>
</div>
<div v-if="accessType == 'download-set'">
+1 -1
View File
@@ -13,7 +13,7 @@
<script src="/ui/app-4f0c6935250753e9.js" defer integrity="sha384-SrAfGs5pnGcgL5E78t2MszNYmdkGME9yPgtPsDg61Rljw+dfiP+rabjPnrMkWDUH" type="module"></script>
<script src="/ui/Downloads-3c8cba0572aebfae.js" integrity="sha384-Pof/sPwhdqKGp7NUmYkDc0pSSyACabwLjkRRylZKHxRmiHBV3SGomPUMAWG9y7Ix"></script>
<script src="/ui/GetCopy-2e04b7b63750e25a.js" integrity="sha384-sNvTYXhjog3BkYL20RzrUWIG9VGyHNO8oEiP5oxL5f2WKmLj/03fdR79FgHfzlj+"></script>
<script src="/ui/ClusterAccess-1090c2ac32649bb9.js" integrity="sha384-xs5yHZycLFkyMIIULJMMeDk04sly74uUtO7qaagZcLLKos9YIOv0sEIJkXRveno3"></script>
<script src="/ui/ClusterAccess-f38a8132c1c029e.js" integrity="sha384-jDiukS9Hz3HCNpLEoV9jdljj5UmvzS95TCY6U+cjSl07JzhgKRvwYwBi8jNoxFBH"></script>
<script src="/ui/ClusterCAs-4f0a381bfb0c1b88.js" integrity="sha384-QcSHKzEHNSCqyjFELXfYFAdfmIZZwJHDzPqI2prH47jHrkeomJD60rcEMLkVAO3o"></script>
<script src="/ui/Cluster-34a012ee0910827c.js" integrity="sha384-Q0sXP0WFJu4gHOI/8NMOGwpJvvFDNBR3T/vDCYxCWL2szAu4+sQuwHm81Jcad/ve"></script>
<script src="/ui/Host-61916516a854adff.js" integrity="sha384-/wh3KrC0sb4MT7ekO2U84rswxI42WSH/0jB4dbDaaGaGhX60xTEZHFsdQAf7UgTG"></script>
+25 -18
View File
@@ -8,13 +8,11 @@ const ClusterAccess = {
PubKey: "",
Principal: "root",
},
sshUserCert: null,
kubeSignReq: {
CSR: "",
User: "",
Group: "system:masters",
},
kubeUserCert: null,
downloadSet: null,
selectedAssets: {},
loading: false,
@@ -30,8 +28,8 @@ const ClusterAccess = {
headers: { 'Content-Type': 'application/json' },
}).then(r => {
if (r.ok) r.json().then(d => {
if (d.user) this.kubeSignReq.User = d.user;
if (d.group) this.kubeSignReq.Group = d.group;
this.kubeSignReq.User = d.user || "anonymous";
this.kubeSignReq.Group = d.group;
});
}).catch(() => {});
}
@@ -73,7 +71,15 @@ const ClusterAccess = {
headers: { 'Authorization': 'Bearer ' + this.token, 'Content-Type': 'application/json' },
}).then((resp) => {
if (resp.ok) {
resp.blob().then((cert) => { this.sshUserCert = URL.createObjectURL(cert); this.loading = false })
resp.blob().then((cert) => {
const url = URL.createObjectURL(cert);
const a = document.createElement('a');
const parts = this.sshSignReq.PubKey.trim().split(/\s+/);
const user = parts.length > 2 ? parts[parts.length - 1] : this.sshSignReq.Principal;
a.href = url; a.download = user + '-cert.pub';
a.click(); URL.revokeObjectURL(url);
this.loading = false;
})
} else {
resp.json().then((resp) => { this.msg = 'failed to sign: '+resp.message; this.loading = false })
}
@@ -89,7 +95,13 @@ const ClusterAccess = {
headers: { 'Authorization': 'Bearer ' + this.token, 'Content-Type': 'application/json' },
}).then((resp) => {
if (resp.ok) {
resp.blob().then((cert) => { this.kubeUserCert = URL.createObjectURL(cert); this.loading = false })
resp.blob().then((cert) => {
const url = URL.createObjectURL(cert);
const a = document.createElement('a');
a.href = url; a.download = (this.kubeSignReq.User || this.kubeSignReq.Group) + '.crt';
a.click(); URL.revokeObjectURL(url);
this.loading = false;
})
} else {
resp.json().then((resp) => { this.msg = 'failed to sign: '+resp.message; this.loading = false })
}
@@ -152,7 +164,8 @@ const ClusterAccess = {
<label class="radio"><input type="radio" v-model="accessType" value="download-set" /> Download set</label>
</p>
<p>Validity: <input type="text" v-model="signReqValidity"/> <small>time range, ie: -5m:1w, 5m, 1M, 1y, 1d-1s, etc.</small></p>
<p>Validity: <input type="text" v-model="signReqValidity"/>
← <template v-for="(v,i) in ['5m','1d','1w','1M','1y']">{{ i ? ', ' : ' ' }}<a href="#" @click.prevent="signReqValidity=v">{{ v }}</a></template></p>
<p class="error" v-if="msg">{{ msg }} <button class="btn-close" @click="msg=null">&times;</button></p>
@@ -163,26 +176,20 @@ const ClusterAccess = {
<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 :disabled="loading" @click="sshCASign">{{ loading ? 'signing...' : 'Sign SSH access' }}</button>
<template v-if="sshUserCert">
=&gt; <a :href="sshUserCert" download="ssh-cert.pub">Get certificate</a>
</template>
</p>
<p><button :disabled="loading" @click="sshCASign">{{ loading ? 'signing...' : 'Sign SSH access' }}</button></p>
</div>
<div v-if="accessType == 'kube'">
<h4>Grant Kubernetes API access</h4>
<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 :disabled="loading" @click="kubeCASign">{{ loading ? 'signing...' : 'Sign Kubernetes API access' }}</button>
<template v-if="kubeUserCert">
=&gt; <a :href="kubeUserCert" download="tls.crt">Get certificate</a>
</template>
<p>User: <input type="text" v-model="kubeSignReq.User"/></p>
<p>Group: <input type="text" v-model="kubeSignReq.Group"/>
<a href="#" @click.prevent="kubeSignReq.Group=''">clear</a>, <a href="#" @click.prevent="kubeSignReq.Group='system:masters'">system:masters</a>
</p>
<p><button :disabled="loading" @click="kubeCASign">{{ loading ? 'signing...' : 'Sign Kubernetes API access' }}</button></p>
</div>
<div v-if="accessType == 'download-set'">