Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,14 @@ func (c *Config) OrchestratorPullFrequency() int {
return intVal
}

func (c *Config) OrchestratorPublicUrl() string {
url := c.GetKey(constants.ORCHESTRATOR_PUBLIC_URL)
Comment thread
cjlapao marked this conversation as resolved.
if url == "" {
return "localhost"
}
return url
}

func (c *Config) DatabaseFolder() string {
return c.GetKey(constants.DATABASE_FOLDER_ENV_VAR)
}
Expand Down
1 change: 1 addition & 0 deletions src/constants/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const (
MODE_ENV_VAR = "MODE"
USE_ORCHESTRATOR_RESOURCES_ENV_VAR = "USE_ORCHESTRATOR_RESOURCES"
ORCHESTRATOR_PULL_FREQUENCY_SECONDS_ENV_VAR = "ORCHESTRATOR_PULL_FREQUENCY_SECONDS"
ORCHESTRATOR_PUBLIC_URL = "ORCHESTRATOR_PUBLIC_URL"
DATABASE_FOLDER_ENV_VAR = "DATABASE_FOLDER"
DATABASE_NUMBER_BACKUP_FILES_ENV_VAR = "DATABASE_NUMBER_BACKUP_FILES"
DATABASE_BACKUP_INTERVAL_ENV_VAR = "DATABASE_BACKUP_INTERVAL_MINUTES"
Expand Down
80 changes: 80 additions & 0 deletions src/controllers/machines.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package controllers

import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -13,7 +14,9 @@ import (
catalog_models "github.com/Parallels/prl-devops-service/catalog/models"
"github.com/Parallels/prl-devops-service/config"
"github.com/Parallels/prl-devops-service/constants"
data_models "github.com/Parallels/prl-devops-service/data/models"
"github.com/Parallels/prl-devops-service/errors"
"github.com/Parallels/prl-devops-service/helpers"
"github.com/Parallels/prl-devops-service/jobs"
"github.com/Parallels/prl-devops-service/mappers"
"github.com/Parallels/prl-devops-service/models"
Expand Down Expand Up @@ -2072,3 +2075,80 @@ func populateMachineRequestArchitecture(ctx basecontext.ApiContext, request *mod
request.Architecture = arch
return nil
}

// buildLocalCatalogConnection creates a temporary internal API key for catalog access
// Used when orchestrator IS the catalog and needs to provide credentials to remote hosts
func buildLocalCatalogConnection(ctx basecontext.ApiContext, callerID string, jobID string) (string, string, error) {
cfg := config.Get()
apiPort := cfg.ApiPort()
if apiPort == "" {
return "", "", fmt.Errorf("API port not configured")
}

// Determine schema and host
schema := "http"
if cfg.TlsEnabled() {
schema = "https"
}

host := cfg.OrchestratorPublicUrl()
ctx.LogDebugf("[Temp API Key] ORCHESTRATOR_PUBLIC_URL from config: '%s'", host)
ctx.LogDebugf("[Temp API Key] API Port: %s, TLS Enabled: %v, Schema: %s", apiPort, cfg.TlsEnabled(), schema)

// Sanitize host to handle various input formats:
// - Remove protocol prefixes (http://, https://)
// - Remove trailing slashes
// - Remove port numbers (we add apiPort separately)
host = strings.TrimSpace(host)
host = strings.TrimPrefix(host, "https://")
host = strings.TrimPrefix(host, "http://")
host = strings.TrimSuffix(host, "/")
if idx := strings.Index(host, ":"); idx != -1 {
host = host[:idx] // Remove port if present
}
if host == "" {
host = "localhost" // Fallback to localhost if empty after sanitization
}

// Generate unique key name and secret
keyName := "local-catalog-" + jobID
fullSecret := helpers.GenerateId()
// Limit secret to 40 chars (password hashing limitation)
plaintextSecret := fullSecret
if len(plaintextSecret) > 40 {
plaintextSecret = plaintextSecret[:40]
}

db := serviceprovider.Get().JsonDatabase
if err := db.Connect(ctx); err != nil {
return "", "", fmt.Errorf("failed to connect to database: %w", err)
}

tempKey := data_models.ApiKey{
ID: helpers.GenerateId(),
Name: keyName,
Key: keyName,
Secret: plaintextSecret, // Will be hashed by CreateApiKey
Type: "internal",
UserID: callerID,
ExpiresAt: time.Now().Add(2 * time.Hour).Format(time.RFC3339),
}

_, err := db.CreateApiKey(ctx, tempKey)
if err != nil {
return "", "", fmt.Errorf("failed to create temp API key: %w", err)
}

// Connection string format for API key authentication:
// host=<base64_encoded_apikey>@protocol://host:port
// API key format: base64(keyName:secret)
apiKeyValue := keyName + ":" + plaintextSecret
encodedApiKey := base64.StdEncoding.EncodeToString([]byte(apiKeyValue))
connStr := fmt.Sprintf("host=%s@%s://%s:%s", encodedApiKey, schema, host, apiPort)

ctx.LogInfof("[Temp API Key] Created temp key '%s' for job %s", keyName, jobID)
ctx.LogInfof("[Temp API Key] Connection string: host=<base64>@%s://%s:%s", schema, host, apiPort)
ctx.LogInfof("[Temp API Key] Base64 API key starts with: %s...", encodedApiKey[:20])

return connStr, keyName, nil
}
Loading
Loading