Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
55 lines
2.0 KiB
Go
55 lines
2.0 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
"github.com/danielgtaylor/huma/v2"
|
|
)
|
|
|
|
// secretNameRE keeps names to what a config file can reference and a log line can show.
|
|
var secretNameRE = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$`)
|
|
|
|
// maxSecretLen bounds a stored secret; real API tokens are far shorter.
|
|
const maxSecretLen = 4096
|
|
|
|
// SecretBody is the body of PUT /secrets/{name}.
|
|
type SecretBody struct {
|
|
Value string `json:"value" minLength:"1" maxLength:"4096" doc:"The secret. It is encrypted at rest and can never be read back."`
|
|
}
|
|
|
|
func (s *server) registerSecrets(api huma.API) {
|
|
huma.Register(api, huma.Operation{
|
|
OperationID: "put-secret", Method: http.MethodPut, Path: "/api/v1/secrets/{name}", Tags: []string{"secrets"},
|
|
Summary: "Set a named secret (write-only)",
|
|
Description: "Creates or replaces the secret, encrypted with the inventory key. Config files refer to it by name " +
|
|
"(for example a Proxmox cluster's secret_name). There is no way to read a secret back.",
|
|
DefaultStatus: http.StatusNoContent,
|
|
Errors: []int{http.StatusUnprocessableEntity, http.StatusServiceUnavailable},
|
|
}, s.putSecret)
|
|
}
|
|
|
|
func (s *server) putSecret(ctx context.Context, in *struct {
|
|
Name string `path:"name" doc:"Secret name, letters, digits and . _ : -"`
|
|
Body SecretBody
|
|
}) (*struct{}, error) {
|
|
w := s.Secrets
|
|
if w == nil {
|
|
return nil, errNotConfigured("secret store")
|
|
}
|
|
if !secretNameRE.MatchString(in.Name) {
|
|
return nil, huma.Error422UnprocessableEntity("name: letters, digits and . _ : - only, at most 64 characters")
|
|
}
|
|
if in.Body.Value == "" || len(in.Body.Value) > maxSecretLen {
|
|
return nil, huma.Error422UnprocessableEntity("value must be 1 to 4096 bytes")
|
|
}
|
|
if err := w.PutSecret(ctx, in.Name, in.Body.Value); err != nil {
|
|
return nil, s.storeErr(err)
|
|
}
|
|
if err := s.Store.AppendAudit(ctx, actorOf(ctx), "secret.put", "secret:"+in.Name, nil, nil); err != nil { // the value is never audited
|
|
s.Logger.Warn("audit secret.put", "err", err)
|
|
}
|
|
return &struct{}{}, nil
|
|
}
|