Add the Hammerhead control plane
Dashboard and API, accounts (organizations, roles, invitations, 2FA, OIDC single sign-on, API keys, revocable sessions), an encrypted SQLite database, resource logins, alerts and /metrics, mail egress management with DKIM keys, and the .env loader. Split out of the original single repository; depends on Reef for the shared types. The end-to-end tests that run Hammerhead, Blacktip and Bamboo together live in e2e/. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3daa368233
commit
f521fa8668
35 files changed
+10392
-1
No files matched your search
@@ -0,0 +1,140 @@
|
||||
package hammerhead
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed web/index.html
|
||||
var indexHTML []byte
|
||||
|
||||
func page(html []byte) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
h.Set("Content-Type", "text/html; charset=utf-8")
|
||||
h.Set("Cache-Control", "no-store")
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("Referrer-Policy", "no-referrer")
|
||||
h.Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'")
|
||||
w.Write(html)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Handler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
index := page(indexHTML)
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
index(w, r)
|
||||
})
|
||||
|
||||
s.registerAlertRoutes(mux)
|
||||
mux.HandleFunc("GET /metrics", s.metrics)
|
||||
|
||||
// public
|
||||
mux.HandleFunc("GET /api/public", s.publicInfo)
|
||||
mux.HandleFunc("POST /api/setup", s.setup)
|
||||
mux.HandleFunc("POST /api/login", s.login)
|
||||
mux.HandleFunc("POST /api/login/totp", s.loginTOTP)
|
||||
mux.HandleFunc("GET /api/invites/{token}", s.inviteInfo)
|
||||
mux.HandleFunc("POST /api/invites/accept", s.acceptInvite)
|
||||
mux.HandleFunc("GET /auth/oidc/{id}/start", s.oidcStart)
|
||||
mux.HandleFunc("GET /auth/oidc/callback", s.oidcCallback)
|
||||
|
||||
// visitor login for protected ports
|
||||
res := page(resourceHTML)
|
||||
mux.HandleFunc("GET /auth/r/{id}", res)
|
||||
mux.HandleFunc("GET /auth/r/{id}/info", s.resourceInfo)
|
||||
mux.HandleFunc("POST /auth/r/{id}/password", s.resourceAuth("password", s.resourcePassword))
|
||||
mux.HandleFunc("POST /auth/r/{id}/pin", s.resourceAuth("pin", s.resourcePin))
|
||||
mux.HandleFunc("POST /auth/r/{id}/sso", s.resourceAuth("sso", s.resourceSSO))
|
||||
mux.HandleFunc("POST /auth/r/{id}/share", s.resourceAuth("share", s.resourceShare))
|
||||
mux.HandleFunc("POST /auth/r/{id}/email/start", s.resourceAuth("emailstart", s.resourceEmailStart))
|
||||
mux.HandleFunc("POST /auth/r/{id}/email/verify", s.resourceAuth("emailverify", s.resourceEmailVerify))
|
||||
|
||||
// any signed-in user
|
||||
mux.HandleFunc("POST /api/logout", s.withUser(s.logout))
|
||||
mux.HandleFunc("GET /api/me", s.withUser(s.me))
|
||||
mux.HandleFunc("PUT /api/me", s.withUser(s.updateMe))
|
||||
mux.HandleFunc("POST /api/me/password", s.withUser(s.changePassword))
|
||||
mux.HandleFunc("POST /api/me/2fa/setup", s.withUser(s.totpSetup))
|
||||
mux.HandleFunc("POST /api/me/2fa/enable", s.withUser(s.totpEnable))
|
||||
mux.HandleFunc("POST /api/me/2fa/disable", s.withUser(s.totpDisable))
|
||||
mux.HandleFunc("GET /api/me/resources", s.withUser(s.myResources))
|
||||
mux.HandleFunc("GET /api/me/sessions", s.withUser(s.mySessions))
|
||||
mux.HandleFunc("DELETE /api/me/sessions/{id}", s.withUser(s.endSession))
|
||||
mux.HandleFunc("POST /api/me/sessions/revoke-others", s.withUser(s.endOtherSessions))
|
||||
mux.HandleFunc("GET /api/edges", s.withUser(s.listEdges))
|
||||
|
||||
// server administrators
|
||||
mux.HandleFunc("POST /api/edges", s.withServerAdmin(s.saveEdge))
|
||||
mux.HandleFunc("PUT /api/edges/{id}", s.withServerAdmin(s.saveEdge))
|
||||
mux.HandleFunc("DELETE /api/edges/{id}", s.withServerAdmin(s.deleteEdge))
|
||||
mux.HandleFunc("POST /api/orgs", s.withServerAdmin(s.createOrg))
|
||||
mux.HandleFunc("GET /api/admin/users", s.withServerAdmin(s.adminUsers))
|
||||
mux.HandleFunc("POST /api/admin/users", s.withServerAdmin(s.adminCreateUser))
|
||||
mux.HandleFunc("PUT /api/admin/users/{id}", s.withServerAdmin(s.adminUpdateUser))
|
||||
mux.HandleFunc("DELETE /api/admin/users/{id}", s.withServerAdmin(s.adminDeleteUser))
|
||||
mux.HandleFunc("GET /api/admin/idps", s.withServerAdmin(s.listIdPs))
|
||||
mux.HandleFunc("POST /api/admin/idps", s.withServerAdmin(s.saveIdP))
|
||||
mux.HandleFunc("PUT /api/admin/idps/{id}", s.withServerAdmin(s.saveIdP))
|
||||
mux.HandleFunc("DELETE /api/admin/idps/{id}", s.withServerAdmin(s.deleteIdP))
|
||||
mux.HandleFunc("GET /api/admin/settings", s.withServerAdmin(s.getSettings))
|
||||
mux.HandleFunc("PUT /api/admin/settings", s.withServerAdmin(s.putSettings))
|
||||
mux.HandleFunc("POST /api/admin/settings/test-mail", s.withServerAdmin(s.testMail))
|
||||
mux.HandleFunc("GET /api/admin/audit", s.withServerAdmin(func(w http.ResponseWriter, r *http.Request, p *principal) {
|
||||
writeJSON(w, 200, s.logs.queryAudit("", 300, 0))
|
||||
}))
|
||||
|
||||
// organization scope: members read, admins write
|
||||
mux.HandleFunc("GET /api/orgs/{org}/state", s.withOrg(false, s.orgState))
|
||||
mux.HandleFunc("PUT /api/orgs/{org}", s.withOrg(true, s.updateOrg))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}", s.withOrg(true, s.deleteOrg))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/roles", s.withOrg(false, s.listRoles))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/roles", s.withOrg(true, s.saveRole))
|
||||
mux.HandleFunc("PUT /api/orgs/{org}/roles/{role}", s.withOrg(true, s.saveRole))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/roles/{role}", s.withOrg(true, s.deleteRole))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/members", s.withOrg(true, s.listMembers))
|
||||
mux.HandleFunc("PUT /api/orgs/{org}/members/{user}", s.withOrg(true, s.setMemberRole))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/members/{user}", s.withOrg(true, s.removeMember))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/invites", s.withOrg(true, s.listInvites))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/invites", s.withOrg(true, s.createInvite))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/invites/{id}", s.withOrg(true, s.deleteInvite))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/apikeys", s.withOrg(true, s.listKeys))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/apikeys", s.withOrg(true, s.createKey))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/apikeys/{id}", s.withOrg(true, s.deleteKey))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/sites", s.withOrg(true, s.addSite))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/sites/{id}", s.withOrg(true, s.deleteSite))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/sites/{id}/containers", s.withOrg(true, s.siteContainers))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/domains", s.withOrg(true, s.addDomain))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/domains/{id}", s.withOrg(true, s.deleteDomain))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/domains/{id}/check", s.withOrg(true, s.checkDomain))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/mappings", s.withOrg(true, s.addMapping))
|
||||
mux.HandleFunc("PUT /api/orgs/{org}/mappings/{id}", s.withOrg(true, s.putMapping))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/mappings/{id}", s.withOrg(true, s.deleteMapping))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/mappings/{id}/shares", s.withOrg(true, s.listShares))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/mappings/{id}/shares", s.withOrg(true, s.createShare))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/mappings/{id}/revoke", s.withOrg(true, s.revokeVisitors))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/mappings/{id}/shares/{share}", s.withOrg(true, s.deleteShare))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/mail-egress", s.withOrg(true, s.saveMailEgress))
|
||||
mux.HandleFunc("PUT /api/orgs/{org}/mail-egress/{id}", s.withOrg(true, s.saveMailEgress))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/mail-egress/{id}", s.withOrg(true, s.deleteMailEgress))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/dkim", s.withOrg(true, s.listDKIM))
|
||||
mux.HandleFunc("POST /api/orgs/{org}/dkim", s.withOrg(true, s.createDKIM))
|
||||
mux.HandleFunc("DELETE /api/orgs/{org}/dkim/{id}", s.withOrg(true, s.deleteDKIM))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/logs", s.withOrg(true, s.orgLogs))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/stats", s.withOrg(false, s.orgStats))
|
||||
mux.HandleFunc("GET /api/orgs/{org}/traffic", s.withOrg(false, s.orgTraffic))
|
||||
|
||||
// agents
|
||||
mux.HandleFunc("GET /edge/config", s.edgeConfig)
|
||||
mux.HandleFunc("POST /edge/report", s.edgeReport)
|
||||
mux.HandleFunc("POST /edge/logs", s.edgeLogs)
|
||||
mux.HandleFunc("GET /site/config", s.siteConfig)
|
||||
mux.HandleFunc("POST /site/report", s.siteReport)
|
||||
return mux
|
||||
}
|
||||
Reference in new issue
Block a user