Initial import — snapshot from admin host /srv/gosec/gsc-ops-api
This repo had no version control prior to this commit. The import is a
straight snapshot of the working tree at 2026-05-03; the deployed
binary on fihelvop01 was being rebuilt from this source via `make
build` + scp into place, with no upstream review path.
The snapshot already includes one in-flight fix made on 2026-05-03 to
internal/service/persona.go:GetSelfModel — the handler queried
`source` and `strength` columns plus an `is_active = true` filter on
persona.persona_commitments, none of which exist on that table (its
shape is session-bound commitments with `status`, `commitment_meta`,
etc.). The query returned a 500 every time SynapseHub bootstrapped a
persona's self-model, dropping the IdentityConstraints / Commitments /
ConscienceStandards layer from the assembled prompt. The patched
query reads existing columns only (commitment_text, commitment_type),
filters on `status='active'`, and synthesises Source="learned" /
Strength=1.0 to keep the SelfModel response shape stable for callers.
Verified live: `GET /api/v1/personas/70f7cfd9-.../self-model` now
returns 200 with `{identityConstraints:[],commitments:[],
conscienceStandards:[]}` instead of 500.
Future changes go through PRs against this repo — no more bin-only
deploys.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
110
internal/service/pgp.go
Normal file
110
internal/service/pgp.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/gosec/gsc-ops-api/internal/client"
|
||||
"github.com/gosec/gsc-ops-api/pkg/types"
|
||||
)
|
||||
|
||||
// PGPService handles Hockeypuck PGP key operations
|
||||
type PGPService struct {
|
||||
client *client.HockeypuckClient
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
// NewPGPService creates a new PGP service
|
||||
func NewPGPService(hkpClient *client.HockeypuckClient, logger zerolog.Logger) *PGPService {
|
||||
return &PGPService{
|
||||
client: hkpClient,
|
||||
logger: logger.With().Str("service", "pgp").Logger(),
|
||||
}
|
||||
}
|
||||
|
||||
// SearchKeys searches for PGP keys
|
||||
func (s *PGPService) SearchKeys(query string) ([]types.PGPKey, error) {
|
||||
result, err := s.client.SearchKeys(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result == "" {
|
||||
return []types.PGPKey{}, nil
|
||||
}
|
||||
|
||||
return parseMachineReadableIndex(result), nil
|
||||
}
|
||||
|
||||
// GetKey retrieves a PGP key by key ID
|
||||
func (s *PGPService) GetKey(keyID string) (*types.PGPKey, error) {
|
||||
armoredKey, err := s.client.GetKey(keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if armoredKey == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &types.PGPKey{
|
||||
KeyID: keyID,
|
||||
ArmoredKey: armoredKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UploadKey uploads a PGP public key
|
||||
func (s *PGPService) UploadKey(keyText string) error {
|
||||
return s.client.UploadKey(keyText)
|
||||
}
|
||||
|
||||
// DeleteKey deletes a PGP key
|
||||
func (s *PGPService) DeleteKey(keyID string) error {
|
||||
return s.client.DeleteKey(keyID)
|
||||
}
|
||||
|
||||
// parseMachineReadableIndex parses the HKP machine-readable index format
|
||||
func parseMachineReadableIndex(data string) []types.PGPKey {
|
||||
keys := []types.PGPKey{}
|
||||
var current *types.PGPKey
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
fields := strings.Split(line, ":")
|
||||
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch fields[0] {
|
||||
case "pub":
|
||||
if current != nil {
|
||||
keys = append(keys, *current)
|
||||
}
|
||||
current = &types.PGPKey{}
|
||||
if len(fields) > 1 {
|
||||
current.KeyID = fields[1]
|
||||
}
|
||||
if len(fields) > 2 {
|
||||
current.Algorithm = fields[2]
|
||||
}
|
||||
if len(fields) > 4 {
|
||||
current.Created = fields[4]
|
||||
}
|
||||
if len(fields) > 5 {
|
||||
current.Expires = fields[5]
|
||||
}
|
||||
case "uid":
|
||||
if current != nil && len(fields) > 1 {
|
||||
current.UIDs = append(current.UIDs, fields[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if current != nil {
|
||||
keys = append(keys, *current)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
Reference in New Issue
Block a user