-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
User Story
As a FlowForge system, I want to securely clone and interact with Git repositories using both SSH and HTTPS authentication, so that user repositories can be accessed safely without exposing credentials.
Acceptance Criteria
- Git client supports both SSH and HTTPS authentication methods
- SSH keys are stored securely and never exposed in logs or errors
- HTTPS credentials (PAT/username+password) are encrypted at rest
- Git operations timeout after configurable duration (default: 5 minutes)
- Repository URLs are validated against whitelist patterns
- Failed authentication attempts are logged with rate limiting
- Support for custom SSH configurations (port, known_hosts)
- Credentials are isolated per job and cleaned up after execution
- Git client respects proxy settings for corporate environments
- All Git commands are executed with minimal required permissions
Technical Implementation
Git Client Service Structure
// pkg/git/client.go
package git
import (
"context"
"crypto/rsa"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
)
type GitClient struct {
config *Config
validator *URLValidator
credManager *CredentialManager
logger *Logger
}
type Config struct {
Timeout time.Duration
MaxRetries int
AllowedURLPatterns []string
ProxyURL string
SSHKeyPath string
KnownHostsPath string
}
type Credentials struct {
Type CredentialType // SSH, HTTPS_TOKEN, HTTPS_BASIC
SSHKey *SSHKeyData
HTTPSAuth *HTTPSAuthData
}
func (c *GitClient) Clone(ctx context.Context, url string, creds Credentials) (*git.Repository, error) {
// Validate URL against whitelist
if err := c.validator.ValidateURL(url); err != nil {
return nil, fmt.Errorf("invalid repository URL: %w", err)
}
// Create auth method based on credentials
auth, err := c.createAuthMethod(creds)
if err != nil {
return nil, fmt.Errorf("failed to create auth: %w", err)
}
// Clone with timeout
cloneCtx, cancel := context.WithTimeout(ctx, c.config.Timeout)
defer cancel()
return git.PlainCloneContext(cloneCtx, path, false, &git.CloneOptions{
URL: url,
Auth: auth,
Progress: c.logger,
SingleBranch: true,
Depth: 1,
RecurseSubmodules: git.NoRecurseSubmodules,
})
}Credential Management
// pkg/git/credentials.go
type CredentialManager struct {
encryption *EncryptionService
storage CredentialStorage
}
func (m *CredentialManager) StoreCredentials(jobID string, creds Credentials) error {
encrypted, err := m.encryption.Encrypt(creds)
if err != nil {
return fmt.Errorf("encryption failed: %w", err)
}
// Store with TTL
return m.storage.Store(jobID, encrypted, 30*time.Minute)
}
func (m *CredentialManager) GetCredentials(jobID string) (*Credentials, error) {
encrypted, err := m.storage.Get(jobID)
if err != nil {
return nil, fmt.Errorf("credential not found: %w", err)
}
return m.encryption.Decrypt(encrypted)
}
// Clean up after job completion
func (m *CredentialManager) CleanupCredentials(jobID string) error {
return m.storage.Delete(jobID)
}Security Validation
// pkg/git/validator.go
type URLValidator struct {
allowedPatterns []*regexp.Regexp
blockedDomains []string
}
func (v *URLValidator) ValidateURL(url string) error {
// Parse URL
parsed, err := giturl.Parse(url)
if err != nil {
return fmt.Errorf("invalid URL format: %w", err)
}
// Check against blocklist
for _, blocked := range v.blockedDomains {
if strings.Contains(parsed.Host, blocked) {
return fmt.Errorf("blocked domain: %s", parsed.Host)
}
}
// Check against allowlist patterns
allowed := false
for _, pattern := range v.allowedPatterns {
if pattern.MatchString(url) {
allowed = true
break
}
}
if !allowed {
return fmt.Errorf("URL not in allowlist: %s", url)
}
return nil
}Architecture References
Git Service Design
Reference: /docs/02-system-components.md:222-367
The Git Service implements secure repository operations with comprehensive validation:
class SecureGitService:
def __init__(self, validation_service, security_scanner):
self.validation = validation_service
self.security = security_scanner
self.conflict_resolver = ConflictResolver()
async def clone_repository(self, url: str, branch: str, depth: int = 1) -> Repository:
"""Clone repository with shallow depth and security checks"""
# Validate repository URL
if not self.security.validate_git_url(url):
raise SecurityException("Invalid or unauthorized repository URL")Security Considerations
Reference: /docs/01-architecture-overview.md:153-172
The architecture emphasizes security-first design:
- Encryption at rest for all sensitive data
- TLS 1.3 for all communications
- Temporary credentials for Git operations
- Audit logging for compliance
Database Schema for Credentials
Reference: /docs/02-system-components.md:535-551
CREATE TABLE repositories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
credentials JSONB, -- Encrypted
settings JSONB DEFAULT '{}',
...
);Dependencies
- go-git/v5: Pure Go Git implementation
- golang.org/x/crypto: For SSH key handling
- PostgreSQL: Credential storage (encrypted)
- Redis: Temporary credential caching
Definition of Done
- Unit tests cover all authentication methods with 90%+ coverage
- Integration tests verify Git operations against test repositories
- Security scan passes with no high/critical vulnerabilities
- Documentation includes credential setup guide
- Performance tests show <2s for typical clone operations
- Error messages don't expose sensitive information
- Credentials are properly cleaned up in all code paths
Effort Estimate
8 Story Points - Complex security requirements and multiple auth methods
Labels
- backend
- git
- security
- priority-high
- epic-5