mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-25 18:19:19 +00:00
232 lines
8.8 KiB
Go
232 lines
8.8 KiB
Go
package http
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
type agentCredentialEntry struct {
|
|
ID uuid.UUID `json:"id"`
|
|
BinaryID uuid.UUID `json:"binary_id"`
|
|
AgentID uuid.UUID `json:"agent_id"`
|
|
AgentKey string `json:"agent_key,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
HasSecret bool `json:"has_secret"`
|
|
EnvKeys []string `json:"env_keys,omitempty"`
|
|
Env map[string]store.SecureCLIEnvResponseEntry `json:"env,omitempty"`
|
|
CredentialType *string `json:"credential_type,omitempty"`
|
|
HostScope *string `json:"host_scope,omitempty"`
|
|
CreatedBy string `json:"created_by,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
func (h *SecureCLIHandler) requireAgentCredentialStore(w http.ResponseWriter, r *http.Request) (store.SecureCLIAgentCredentialStore, bool) {
|
|
if h.agentCreds == nil {
|
|
locale := store.LocaleFromContext(r.Context())
|
|
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, "agent credentials store unavailable")})
|
|
return nil, false
|
|
}
|
|
return h.agentCreds, true
|
|
}
|
|
|
|
func (h *SecureCLIHandler) requireAgentCredentialAdmin(w http.ResponseWriter, r *http.Request) bool {
|
|
return requireTenantAdmin(w, r, h.tenants)
|
|
}
|
|
|
|
func agentCredentialResponse(c store.SecureCLIAgentCredential) agentCredentialEntry {
|
|
isTyped := c.CredentialType != nil && *c.CredentialType != "" && *c.CredentialType != "env"
|
|
e := agentCredentialEntry{
|
|
ID: c.ID,
|
|
BinaryID: c.BinaryID,
|
|
AgentID: c.AgentID,
|
|
AgentKey: c.AgentKey,
|
|
Name: c.Name,
|
|
HasSecret: len(c.EncryptedEnv) > 0,
|
|
CredentialType: c.CredentialType,
|
|
HostScope: c.HostScope,
|
|
CreatedBy: c.CreatedBy,
|
|
CreatedAt: c.CreatedAt,
|
|
UpdatedAt: c.UpdatedAt,
|
|
}
|
|
if !isTyped {
|
|
e.EnvKeys = envKeysFromDecryptedJSON(c.EncryptedEnv)
|
|
e.Env = store.SanitizeSecureCLIEnvJSON(c.EncryptedEnv)
|
|
}
|
|
return e
|
|
}
|
|
|
|
func parseAgentCredentialPath(w http.ResponseWriter, r *http.Request, locale string) (uuid.UUID, uuid.UUID, bool) {
|
|
binaryID, err := uuid.Parse(r.PathValue("id"))
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "credential")})
|
|
return uuid.Nil, uuid.Nil, false
|
|
}
|
|
agentID, err := uuid.Parse(r.PathValue("agentId"))
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "agent")})
|
|
return uuid.Nil, uuid.Nil, false
|
|
}
|
|
return binaryID, agentID, true
|
|
}
|
|
|
|
func (h *SecureCLIHandler) handleListAgentCredentials(w http.ResponseWriter, r *http.Request) {
|
|
if !h.requireAgentCredentialAdmin(w, r) {
|
|
return
|
|
}
|
|
agentCreds, ok := h.requireAgentCredentialStore(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
locale := store.LocaleFromContext(r.Context())
|
|
binaryID, err := uuid.Parse(r.PathValue("id"))
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "credential")})
|
|
return
|
|
}
|
|
creds, err := agentCreds.ListAgentCredentials(r.Context(), binaryID)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, err.Error())})
|
|
return
|
|
}
|
|
entries := make([]agentCredentialEntry, 0, len(creds))
|
|
for _, c := range creds {
|
|
entries = append(entries, agentCredentialResponse(c))
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"agent_credentials": entries})
|
|
}
|
|
|
|
func (h *SecureCLIHandler) handleGetAgentCredentials(w http.ResponseWriter, r *http.Request) {
|
|
if !h.requireAgentCredentialAdmin(w, r) {
|
|
return
|
|
}
|
|
agentCreds, ok := h.requireAgentCredentialStore(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
locale := store.LocaleFromContext(r.Context())
|
|
binaryID, agentID, ok := parseAgentCredentialPath(w, r, locale)
|
|
if !ok {
|
|
return
|
|
}
|
|
cred, err := agentCreds.GetAgentCredentials(r.Context(), binaryID, agentID)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, err.Error())})
|
|
return
|
|
}
|
|
if cred == nil {
|
|
writeJSON(w, http.StatusNotFound, map[string]string{"error": i18n.T(locale, i18n.MsgNotFound, "agent credential", agentID.String())})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, agentCredentialResponse(*cred))
|
|
}
|
|
|
|
func (h *SecureCLIHandler) handleSetAgentCredentials(w http.ResponseWriter, r *http.Request) {
|
|
if !h.requireAgentCredentialAdmin(w, r) {
|
|
return
|
|
}
|
|
agentCreds, ok := h.requireAgentCredentialStore(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
locale := store.LocaleFromContext(r.Context())
|
|
binaryID, agentID, ok := parseAgentCredentialPath(w, r, locale)
|
|
if !ok {
|
|
return
|
|
}
|
|
if exists, err := agentCreds.BinaryExists(r.Context(), binaryID); err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, "validate credential")})
|
|
return
|
|
} else if !exists {
|
|
writeJSON(w, http.StatusNotFound, map[string]string{"error": i18n.T(locale, i18n.MsgNotFound, "credential", binaryID.String())})
|
|
return
|
|
}
|
|
if exists, err := agentCreds.AgentExists(r.Context(), agentID); err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, "validate agent")})
|
|
return
|
|
} else if !exists {
|
|
writeJSON(w, http.StatusNotFound, map[string]string{"error": i18n.T(locale, i18n.MsgNotFound, "agent", agentID.String())})
|
|
return
|
|
}
|
|
|
|
var body struct {
|
|
Env json.RawMessage `json:"env"`
|
|
typedCredentialBody
|
|
}
|
|
if !bindJSON(w, r, locale, &body) {
|
|
return
|
|
}
|
|
envBytes, credType, hostScope, terr := prepareTypedCredentialEnv(r.Context(), locale, body.typedCredentialBody)
|
|
if terr != nil {
|
|
writeTypedCredentialError(w, terr)
|
|
return
|
|
}
|
|
createdBy := store.UserIDFromContext(r.Context())
|
|
if envBytes != nil {
|
|
if err := agentCreds.SetAgentCredentialsTyped(r.Context(), binaryID, agentID, envBytes, credType, hostScope, createdBy); err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, err.Error())})
|
|
return
|
|
}
|
|
emitAudit(h.msgBus, r, "secure_cli.agent_credentials.updated", "secure_cli_agent_credentials", binaryID.String()+"/"+agentID.String()+"#"+*credType)
|
|
h.emitCacheInvalidate("")
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
return
|
|
}
|
|
if len(body.Env) == 0 {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "env")})
|
|
return
|
|
}
|
|
existing, err := agentCreds.GetAgentCredentials(r.Context(), binaryID, agentID)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, err.Error())})
|
|
return
|
|
}
|
|
var existingEnv []byte
|
|
if existing != nil {
|
|
existingEnv = existing.EncryptedEnv
|
|
}
|
|
envJSON, err := store.MergeSecureCLIEnv(existingEnv, body.Env)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgGrantEnvValueInvalid, err.Error())})
|
|
return
|
|
}
|
|
envJSON, ok = validateAndSerializeEnvVars(w, locale, json.RawMessage(envJSON))
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := agentCreds.SetAgentCredentials(r.Context(), binaryID, agentID, envJSON, createdBy); err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, err.Error())})
|
|
return
|
|
}
|
|
emitAudit(h.msgBus, r, "secure_cli.agent_credentials.updated", "secure_cli_agent_credentials", binaryID.String()+"/"+agentID.String())
|
|
h.emitCacheInvalidate("")
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func (h *SecureCLIHandler) handleDeleteAgentCredentials(w http.ResponseWriter, r *http.Request) {
|
|
if !h.requireAgentCredentialAdmin(w, r) {
|
|
return
|
|
}
|
|
agentCreds, ok := h.requireAgentCredentialStore(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
locale := store.LocaleFromContext(r.Context())
|
|
binaryID, agentID, ok := parseAgentCredentialPath(w, r, locale)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := agentCreds.DeleteAgentCredentials(r.Context(), binaryID, agentID); err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, err.Error())})
|
|
return
|
|
}
|
|
emitAudit(h.msgBus, r, "secure_cli.agent_credentials.deleted", "secure_cli_agent_credentials", binaryID.String()+"/"+agentID.String())
|
|
h.emitCacheInvalidate("")
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|