Add SSH Key Management
This commit is contained in:
parent
ef76844922
commit
bcd1805001
62 changed files with 1004 additions and 188 deletions
|
|
@ -134,14 +134,6 @@
|
|||
<code>{{.Config.ConfigPath}}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>authorized_keys</code>
|
||||
</td>
|
||||
<td>
|
||||
<code>{{.Config.GlobalAuthorizedKeysFile}}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
|
|||
{Title: "Admin", Path: "/admin/"},
|
||||
{Title: "Users", Path: "/admin/users/"},
|
||||
},
|
||||
Actions: []component.MenuItem{
|
||||
{Title: "Create New", Path: "/admin/users/create/"},
|
||||
},
|
||||
})
|
||||
|
||||
uc.Error = r.URL.Query().Get("error")
|
||||
|
|
@ -70,9 +73,6 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler {
|
|||
{Title: "Users", Path: "/admin/users"},
|
||||
{Title: "Create", Path: "/admin/users/create"},
|
||||
},
|
||||
Actions: []component.MenuItem{
|
||||
{Title: "Create New", Path: "/admin/users/create/"},
|
||||
},
|
||||
}
|
||||
|
||||
return &httpx.Form[createUserResult]{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ HOST_RULE=${HOST_RULE}
|
|||
|
||||
CONFIG_PATH=${CONFIG_PATH}
|
||||
DEPLOY_ROOT=${DEPLOY_ROOT}
|
||||
GLOBAL_AUTHORIZED_KEYS_FILE=${GLOBAL_AUTHORIZED_KEYS_FILE}
|
||||
SELF_OVERRIDES_FILE=${SELF_OVERRIDES_FILE}
|
||||
SELF_RESOLVER_BLOCK_FILE=${SELF_RESOLVER_BLOCK_FILE}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,8 @@ func (control *Control) Stack(env environment.Environment) component.StackWithRe
|
|||
"CONFIG_PATH": control.Config.ConfigPath,
|
||||
"DEPLOY_ROOT": control.Config.DeployRoot,
|
||||
|
||||
"GLOBAL_AUTHORIZED_KEYS_FILE": control.Config.GlobalAuthorizedKeysFile,
|
||||
"SELF_OVERRIDES_FILE": control.Config.SelfOverridesFile,
|
||||
"SELF_RESOLVER_BLOCK_FILE": control.Config.SelfResolverBlockFile,
|
||||
"SELF_OVERRIDES_FILE": control.Config.SelfOverridesFile,
|
||||
"SELF_RESOLVER_BLOCK_FILE": control.Config.SelfResolverBlockFile,
|
||||
|
||||
"CUSTOM_ASSETS_PATH": control.Dependencies.Custom.CustomAssetsPath(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ FROM docker.io/library/docker:20.10-cli
|
|||
|
||||
COPY wdcli /wdcli
|
||||
EXPOSE 8888
|
||||
CMD ["/wdcli","--internal-in-docker","server","--bind","0.0.0.0:8888"]
|
||||
CMD ["/wdcli","--internal-in-docker","server","--bind","0.0.0.0:8888", "--internal-bind", "0.0.0.0:9999"]
|
||||
|
|
@ -25,7 +25,6 @@ services:
|
|||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "${CONFIG_PATH}:${CONFIG_PATH}:ro"
|
||||
- "${DEPLOY_ROOT}:${DEPLOY_ROOT}:rw"
|
||||
- "${GLOBAL_AUTHORIZED_KEYS_FILE}:${GLOBAL_AUTHORIZED_KEYS_FILE}:ro"
|
||||
- "${SELF_OVERRIDES_FILE}:${SELF_OVERRIDES_FILE}:ro"
|
||||
- "${SELF_RESOLVER_BLOCK_FILE}:${SELF_RESOLVER_BLOCK_FILE}:ro"
|
||||
- "${CUSTOM_ASSETS_PATH}:${CUSTOM_ASSETS_PATH}:ro"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Reworked SSH key support
|
||||
date: 2023-01-15
|
||||
---
|
||||
|
||||
- reworked and added ssh key management to the server
|
||||
- users can now add and remove ssh keys to their account
|
||||
- each user with an admin grant for a specific instance has ssh access via their keys
|
||||
- distillery administrators have implicit access to all instances
|
||||
|
|
@ -18,17 +18,17 @@ import (
|
|||
// The server may spawn background tasks, but these should be terminated once context closes.
|
||||
//
|
||||
// Logging messages are directed to progress
|
||||
func (control *Control) Server(ctx context.Context, progress io.Writer) (http.Handler, error) {
|
||||
func (control *Control) Server(ctx context.Context, progress io.Writer) (public http.Handler, internal http.Handler, err error) {
|
||||
logger := zerolog.Ctx(ctx)
|
||||
|
||||
var mux mux.Mux[component.RouteContext]
|
||||
mux.Context = func(r *http.Request) component.RouteContext {
|
||||
var publicM, internalM mux.Mux[component.RouteContext]
|
||||
publicM.Context = func(r *http.Request) component.RouteContext {
|
||||
slug, ok := control.Still.Config.SlugFromHost(r.Host)
|
||||
return component.RouteContext{
|
||||
DefaultDomain: slug == "" && ok,
|
||||
}
|
||||
}
|
||||
mux.Panic = func(panic any, w http.ResponseWriter, r *http.Request) {
|
||||
publicM.Panic = func(panic any, w http.ResponseWriter, r *http.Request) {
|
||||
// log the panic
|
||||
logger.Error().
|
||||
Str("panic", fmt.Sprint(panic)).
|
||||
|
|
@ -39,6 +39,10 @@ func (control *Control) Server(ctx context.Context, progress io.Writer) (http.Ha
|
|||
httpx.TextInterceptor.Fallback.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// setup the internal server identically
|
||||
internalM.Panic = publicM.Panic
|
||||
internalM.Context = publicM.Context
|
||||
|
||||
// create a csrf protector
|
||||
csrfProtector := control.CSRF()
|
||||
|
||||
|
|
@ -52,6 +56,7 @@ func (control *Control) Server(ctx context.Context, progress io.Writer) (http.Ha
|
|||
Bool("Exact", routes.Exact).
|
||||
Bool("CSRF", routes.CSRF).
|
||||
Bool("Decorator", routes.Decorator != nil).
|
||||
Bool("Internal", routes.Internal).
|
||||
Bool("MatchAllDomains", routes.MatchAllDomains).
|
||||
Msg("mounting route")
|
||||
|
||||
|
|
@ -69,19 +74,23 @@ func (control *Control) Server(ctx context.Context, progress io.Writer) (http.Ha
|
|||
handler = routes.Decorate(handler, csrfProtector)
|
||||
|
||||
// determine the predicate
|
||||
predicate := routes.Predicate(mux.ContextOf)
|
||||
predicate := routes.Predicate(publicM.ContextOf)
|
||||
|
||||
// and add all the prefixes
|
||||
for _, prefix := range append([]string{routes.Prefix}, routes.Aliases...) {
|
||||
mux.Add(prefix, predicate, routes.Exact, handler)
|
||||
if routes.Internal {
|
||||
internalM.Add(prefix, predicate, routes.Exact, handler)
|
||||
} else {
|
||||
publicM.Add(prefix, predicate, routes.Exact, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply the given context function
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(cancel.ValuesOf(r.Context(), ctx))
|
||||
mux.ServeHTTP(w, r)
|
||||
}), nil
|
||||
public = httpx.WithContextWrapper(&publicM, func(rcontext context.Context) context.Context { return cancel.ValuesOf(rcontext, ctx) })
|
||||
internal = httpx.WithContextWrapper(&internalM, func(rcontext context.Context) context.Context { return cancel.ValuesOf(rcontext, ctx) })
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
// CSRF returns a CSRF handler for the given function
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ var AssetsDefault = Assets{
|
|||
|
||||
// AssetsUser contains assets for the 'User' entrypoint.
|
||||
var AssetsUser = Assets{
|
||||
Scripts: `<script type="module" src="/static/Default.38d394c2.js"></script><script src="/static/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/User.4197014b.js"></script><script src="/static/User.30d54198.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/Default.db26a303.css"><link rel="stylesheet" href="/static/User.38d394c2.css">`,
|
||||
Scripts: `<script type="module" src="/static/Default.38d394c2.js"></script><script src="/static/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/User.e0367d79.js"></script><script src="/static/User.b2f9a57c.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/Default.db26a303.css"><link rel="stylesheet" href="/static/User.68febbf8.css"><link rel="stylesheet" href="/static/User.840de3b4.css">`,
|
||||
}
|
||||
|
||||
// AssetsAdmin contains assets for the 'Admin' entrypoint.
|
||||
var AssetsAdmin = Assets{
|
||||
Scripts: `<script nomodule="" defer src="/static/User.30d54198.js"></script><script type="module" src="/static/User.4197014b.js"></script><script type="module" src="/static/Default.38d394c2.js"></script><script src="/static/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Admin.4ca3cb6f.js"></script><script src="/static/Admin.9750ba9c.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/Default.db26a303.css"><link rel="stylesheet" href="/static/Admin.6d59e220.css"><link rel="stylesheet" href="/static/User.38d394c2.css"><link rel="stylesheet" href="/static/Admin.6d2ae968.css">`,
|
||||
Scripts: `<script nomodule="" defer src="/static/User.b2f9a57c.js"></script><script type="module" src="/static/User.e0367d79.js"></script><script type="module" src="/static/Default.38d394c2.js"></script><script src="/static/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Admin.4ca3cb6f.js"></script><script src="/static/Admin.9750ba9c.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/Default.db26a303.css"><link rel="stylesheet" href="/static/Admin.6d59e220.css"><link rel="stylesheet" href="/static/User.840de3b4.css"><link rel="stylesheet" href="/static/User.68febbf8.css"><link rel="stylesheet" href="/static/Admin.6d2ae968.css">`,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},r={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in r){var o=r[e];delete r[e];var i={id:e,exports:{}};return n[e]=i,o.call(i.exports,i,i.exports),i.exports}var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}).register=function(e,n){r[e]=n},e.parcelRequireafa4=o),o.register("kEAtK",(function(e,n){})),o("kEAtK")}();
|
||||
|
|
@ -1 +0,0 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},r={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in r){var o=r[e];delete r[e];var i={id:e,exports:{}};return n[e]=i,o.call(i.exports,i,i.exports),i.exports}var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}).register=function(e,n){r[e]=n},e.parcelRequireafa4=o),o.register("gkpdw",(function(e,n){})),o("gkpdw");
|
||||
1
internal/dis/component/control/static/dist/User.68febbf8.css
vendored
Normal file
1
internal/dis/component/control/static/dist/User.68febbf8.css
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.copy{-webkit-user-select:all;user-select:all}
|
||||
1
internal/dis/component/control/static/dist/User.840de3b4.css
vendored
Normal file
1
internal/dis/component/control/static/dist/User.840de3b4.css
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
textarea#key{width:50%;height:10em;resize:both}
|
||||
1
internal/dis/component/control/static/dist/User.b2f9a57c.js
vendored
Normal file
1
internal/dis/component/control/static/dist/User.b2f9a57c.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},r={},n={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in r)return r[e].exports;if(e in n){var o=n[e];delete n[e];var t={id:e,exports:{}};return r[e]=t,o.call(t.exports,t,t.exports),t.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,r){n[e]=r},e.parcelRequireafa4=o),o.register("kEAtK",(function(e,r){o("15EWx")})),o.register("15EWx",(function(e,r){document.querySelectorAll(".copy").forEach((e=>{e.addEventListener("click",(()=>{navigator.clipboard&&navigator.clipboard.writeText(e.innerText)}))}))})),o("kEAtK")}();
|
||||
1
internal/dis/component/control/static/dist/User.e0367d79.js
vendored
Normal file
1
internal/dis/component/control/static/dist/User.e0367d79.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},r={},n={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in r)return r[e].exports;if(e in n){var o=n[e];delete n[e];var i={id:e,exports:{}};return r[e]=i,o.call(i.exports,i,i.exports),i.exports}var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}).register=function(e,r){n[e]=r},e.parcelRequireafa4=o),o.register("gkpdw",(function(e,r){o("hZNgY")})),o.register("hZNgY",(function(e,r){document.querySelectorAll(".copy").forEach((e=>{e.addEventListener("click",(()=>{navigator.clipboard&&navigator.clipboard.writeText(e.innerText)}))}))})),o("gkpdw");
|
||||
|
|
@ -1 +1,6 @@
|
|||
/* nothing for now */
|
||||
/* textarea on the /user/ssh/add form */
|
||||
textarea#key {
|
||||
width: 50%;
|
||||
height: 10em;
|
||||
resize: both;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
// nothing for now
|
||||
import "~/src/lib/copy"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.copy {
|
||||
user-select: all;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import "./index.css"
|
||||
|
||||
document.querySelectorAll('.copy').forEach((elem: Element) => {
|
||||
elem.addEventListener('click', () => {
|
||||
if (!navigator.clipboard) return;
|
||||
navigator.clipboard.writeText((elem as HTMLElement).innerText);
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue