mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 20:15:48 +00:00
feat(cli-credentials): add agent-scoped git credentials
This commit is contained in:
1 parent
e6242dbbbc
commit
994acfe3a4
47 files changed
+2152
-113
No files matched your search
@@ -0,0 +1,193 @@
|
||||
//go:build sqlite || sqliteonly
|
||||
|
||||
package sqlitestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/crypto"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
)
|
||||
|
||||
const agentCredSelectCols = `c.id, c.binary_id, c.agent_id, c.encrypted_env, COALESCE(c.metadata, '{}'),
|
||||
c.credential_type, c.host_scope, c.created_by, c.created_at, c.updated_at`
|
||||
|
||||
func agentCredentialTenantID(ctx context.Context) (uuid.UUID, error) {
|
||||
tid := store.TenantIDFromContext(ctx)
|
||||
if tid == uuid.Nil {
|
||||
return uuid.Nil, fmt.Errorf("tenant_id required for agent credentials")
|
||||
}
|
||||
return tid, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) BinaryExists(ctx context.Context, binaryID uuid.UUID) (bool, error) {
|
||||
tid, err := agentCredentialTenantID(ctx)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
var exists bool
|
||||
err = s.db.QueryRowContext(ctx,
|
||||
`SELECT EXISTS(SELECT 1 FROM secure_cli_binaries WHERE id = ? AND tenant_id = ?)`,
|
||||
binaryID, tid,
|
||||
).Scan(&exists)
|
||||
return exists, err
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) AgentExists(ctx context.Context, agentID uuid.UUID) (bool, error) {
|
||||
tid, err := agentCredentialTenantID(ctx)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
var exists bool
|
||||
err = s.db.QueryRowContext(ctx,
|
||||
`SELECT EXISTS(SELECT 1 FROM agents WHERE id = ? AND tenant_id = ? AND deleted_at IS NULL)`,
|
||||
agentID, tid,
|
||||
).Scan(&exists)
|
||||
return exists, err
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) GetAgentCredentials(ctx context.Context, binaryID uuid.UUID, agentID uuid.UUID) (*store.SecureCLIAgentCredential, error) {
|
||||
tid, err := agentCredentialTenantID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var c store.SecureCLIAgentCredential
|
||||
var env []byte
|
||||
var metaBytes []byte
|
||||
var createdAt, updatedAt string
|
||||
err = s.db.QueryRowContext(ctx,
|
||||
`SELECT `+agentCredSelectCols+`, COALESCE(a.agent_key, ''), COALESCE(a.display_name, '')
|
||||
FROM secure_cli_agent_credentials c
|
||||
LEFT JOIN agents a ON a.id = c.agent_id AND a.tenant_id = c.tenant_id
|
||||
WHERE c.binary_id = ? AND c.agent_id = ? AND c.tenant_id = ?`,
|
||||
binaryID, agentID, tid,
|
||||
).Scan(&c.ID, &c.BinaryID, &c.AgentID, &env, &metaBytes,
|
||||
&c.CredentialType, &c.HostScope, &c.CreatedBy, &createdAt, &updatedAt,
|
||||
&c.AgentKey, &c.Name)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.CreatedAt = createdAt
|
||||
c.UpdatedAt = updatedAt
|
||||
c.Metadata = metaBytes
|
||||
c.EncryptedEnv = s.decryptAgentCredentialEnv(env)
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) SetAgentCredentials(ctx context.Context, binaryID uuid.UUID, agentID uuid.UUID, encryptedEnv []byte, createdBy string) error {
|
||||
return s.SetAgentCredentialsTyped(ctx, binaryID, agentID, encryptedEnv, nil, nil, createdBy)
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) SetAgentCredentialsTyped(ctx context.Context, binaryID uuid.UUID, agentID uuid.UUID, encryptedEnv []byte, credentialType, hostScope *string, createdBy string) error {
|
||||
tid, err := agentCredentialTenantID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envBytes, err := s.encryptAgentCredentialEnv(encryptedEnv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now().UTC().Format(time.RFC3339Nano)
|
||||
id := store.GenNewID()
|
||||
res, err := s.db.ExecContext(ctx,
|
||||
`INSERT INTO secure_cli_agent_credentials
|
||||
(id, binary_id, agent_id, encrypted_env, metadata, tenant_id,
|
||||
credential_type, host_scope, created_by, created_at, updated_at)
|
||||
SELECT ?, b.id, a.id, ?, '{}', ?, ?, ?, ?, ?, ?
|
||||
FROM secure_cli_binaries b
|
||||
JOIN agents a ON a.id = ? AND a.tenant_id = ? AND a.deleted_at IS NULL
|
||||
WHERE b.id = ? AND b.tenant_id = ?
|
||||
ON CONFLICT (binary_id, agent_id, tenant_id) DO UPDATE SET
|
||||
encrypted_env = excluded.encrypted_env,
|
||||
credential_type = excluded.credential_type,
|
||||
host_scope = excluded.host_scope,
|
||||
created_by = excluded.created_by,
|
||||
updated_at = excluded.updated_at`,
|
||||
id, envBytes, tid, credentialType, hostScope, createdBy, now, now,
|
||||
agentID, tid, binaryID, tid,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := res.RowsAffected(); err == nil && n == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) DeleteAgentCredentials(ctx context.Context, binaryID uuid.UUID, agentID uuid.UUID) error {
|
||||
tid, err := agentCredentialTenantID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.db.ExecContext(ctx,
|
||||
`DELETE FROM secure_cli_agent_credentials WHERE binary_id = ? AND agent_id = ? AND tenant_id = ?`,
|
||||
binaryID, agentID, tid,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) ListAgentCredentials(ctx context.Context, binaryID uuid.UUID) ([]store.SecureCLIAgentCredential, error) {
|
||||
tid, err := agentCredentialTenantID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT `+agentCredSelectCols+`, COALESCE(a.agent_key, ''), COALESCE(a.display_name, '')
|
||||
FROM secure_cli_agent_credentials c
|
||||
LEFT JOIN agents a ON a.id = c.agent_id AND a.tenant_id = c.tenant_id
|
||||
WHERE c.binary_id = ? AND c.tenant_id = ?
|
||||
ORDER BY c.created_at`, binaryID, tid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []store.SecureCLIAgentCredential
|
||||
for rows.Next() {
|
||||
var c store.SecureCLIAgentCredential
|
||||
var env []byte
|
||||
var metaBytes []byte
|
||||
var createdAt, updatedAt string
|
||||
if err := rows.Scan(&c.ID, &c.BinaryID, &c.AgentID, &env, &metaBytes,
|
||||
&c.CredentialType, &c.HostScope, &c.CreatedBy, &createdAt, &updatedAt,
|
||||
&c.AgentKey, &c.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.CreatedAt = createdAt
|
||||
c.UpdatedAt = updatedAt
|
||||
c.Metadata = metaBytes
|
||||
c.EncryptedEnv = s.decryptAgentCredentialEnv(env)
|
||||
result = append(result, c)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) encryptAgentCredentialEnv(env []byte) ([]byte, error) {
|
||||
if len(env) == 0 || s.encKey == "" {
|
||||
return env, nil
|
||||
}
|
||||
encrypted, err := crypto.Encrypt(string(env), s.encKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt agent credential env: %w", err)
|
||||
}
|
||||
return []byte(encrypted), nil
|
||||
}
|
||||
|
||||
func (s *SQLiteSecureCLIStore) decryptAgentCredentialEnv(env []byte) []byte {
|
||||
if len(env) > 0 && s.encKey != "" {
|
||||
if decrypted, err := crypto.Decrypt(string(env), s.encKey); err == nil {
|
||||
return []byte(decrypted)
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
Reference in new issue
Block a user