vet/internal/auth/auth.go
2025-08-15 20:26:55 +05:30

357 lines
8.3 KiB
Go

package auth
import (
"fmt"
"os"
"path/filepath"
"strconv"
"time"
"gopkg.in/yaml.v2"
)
const (
apiUrlEnvKey = "VET_INSIGHTS_API_URL"
apiV2UrlEnvKey = "VET_INSIGHTS_API_V2_URL" // gitleaks:allow
communityServicesApiUrlEnvKey = "VET_COMMUNITY_SERVICES_API_URL"
syncUrlEnvKey = "VET_SYNC_API_URL"
controlPlaneUrlEnvKey = "VET_CONTROL_PLANE_API_URL"
dataPlaneUrlEnvKey = "VET_DATA_PLANE_API_URL"
apiKeyEnvKey = "VET_API_KEY"
apiKeyAlternateEnvKey = "VET_INSIGHTS_API_KEY"
communityModeEnvKey = "VET_COMMUNITY_MODE"
controlTowerTenantEnvKey = "VET_CONTROL_TOWER_TENANT_ID"
defaultSafeDepApiKeyEnvKey = "SAFEDEP_API_KEY"
defaultSafeDepTenantIdEnvKey = "SAFEDEP_TENANT_ID"
defaultApiUrl = "https://api.safedep.io/insights/v1"
defaultCommunityApiUrl = "https://api.safedep.io/insights-community/v1"
// gRPC service base URL.
defaultDataPlaneApiUrl = "https://api.safedep.io"
defaultSyncApiUrl = "https://api.safedep.io"
defaultInsightsApiV2Url = "https://api.safedep.io"
defaultControlPlaneApiUrl = "https://cloud.safedep.io"
// Community services is a new unauthenticated endpoint through
// which we can serve the community features without authentication.
defaultCommunityServicesApiUrl = "https://community-api.safedep.io"
homeRelativeConfigPath = ".safedep/vet-auth.yml"
cloudIdentityServiceClientId = "QtXHUN3hOdbJbCiGU8FiNCnC2KtuROCu" // gitleaks:allow
cloudIdentityServiceAudience = "https://cloud.safedep.io"
cloudIdentityServiceBaseUrl = "https://auth.safedep.io"
cloudIdentityServiceDeviceCodeUrl = "https://auth.safedep.io/oauth/device/code"
cloudIdentityServiceTokenUrl = "https://auth.safedep.io/oauth/token"
)
type Config struct {
ApiUrl string `yaml:"api_url"`
ApiKey string `yaml:"api_key"`
Community bool `yaml:"community"`
DataPlaneApiUrl string `yaml:"data_plane_api_url"`
ControlPlaneApiUrl string `yaml:"control_api_url"`
SyncApiUrl string `yaml:"sync_api_url"`
InsightsApiV2Url string `yaml:"insights_api_v2_url"`
CommunityServicesApiUrl string `yaml:"community_services_api_url"`
TenantDomain string `yaml:"tenant_domain"`
CloudAccessToken string `yaml:"cloud_access_token"`
CloudRefreshToken string `yaml:"cloud_refresh_token"`
CloudAccessTokenUpdatedAt time.Time `yaml:"cloud_access_token_updated_at"`
}
// Global config to be used during runtime
var globalConfig *Config
func init() {
_ = loadConfiguration()
}
func DefaultConfig() Config {
return Config{
ApiUrl: defaultApiUrl,
Community: false,
DataPlaneApiUrl: defaultDataPlaneApiUrl,
ControlPlaneApiUrl: defaultControlPlaneApiUrl,
SyncApiUrl: defaultSyncApiUrl,
InsightsApiV2Url: defaultInsightsApiV2Url,
CommunityServicesApiUrl: defaultCommunityServicesApiUrl,
}
}
func PersistApiKey(key, domain string) error {
if globalConfig == nil {
c := DefaultConfig()
globalConfig = &c
}
if domain != "" {
globalConfig.TenantDomain = domain
}
globalConfig.ApiUrl = defaultApiUrl
globalConfig.ApiKey = key
return persistConfiguration()
}
func PersistCloudTokens(accessToken, refreshToken, domain string) error {
if globalConfig == nil {
c := DefaultConfig()
globalConfig = &c
}
// We are explicitly check for empty string for domain
// because we do not want to overwrite the domain if it is
// not provided.
if domain != "" {
globalConfig.TenantDomain = domain
}
globalConfig.CloudAccessToken = accessToken
globalConfig.CloudRefreshToken = refreshToken
globalConfig.CloudAccessTokenUpdatedAt = time.Now()
return persistConfiguration()
}
func PersistTenantDomain(domain string) error {
if globalConfig == nil {
c := DefaultConfig()
globalConfig = &c
}
globalConfig.TenantDomain = domain
return persistConfiguration()
}
func DefaultApiUrl() string {
return defaultApiUrl
}
func DefaultCommunityApiUrl() string {
return defaultCommunityApiUrl
}
func CloudIdentityServiceClientId() string {
return cloudIdentityServiceClientId
}
func CloudIdentityServiceBaseUrl() string {
return cloudIdentityServiceBaseUrl
}
func CloudIdentityServiceDeviceCodeUrl() string {
return cloudIdentityServiceDeviceCodeUrl
}
func CloudIdentityServiceAudience() string {
return cloudIdentityServiceAudience
}
func CloudIdentityServiceTokenUrl() string {
return cloudIdentityServiceTokenUrl
}
func CloudAccessToken() string {
if globalConfig != nil {
return globalConfig.CloudAccessToken
}
return ""
}
func CloudRefreshToken() string {
if globalConfig != nil {
return globalConfig.CloudRefreshToken
}
return ""
}
func DataPlaneUrl() string {
envOverride := os.Getenv(dataPlaneUrlEnvKey)
if envOverride != "" {
return envOverride
}
if (globalConfig != nil) && (globalConfig.DataPlaneApiUrl != "") {
return globalConfig.DataPlaneApiUrl
}
return defaultDataPlaneApiUrl
}
func SyncApiUrl() string {
envOverride := os.Getenv(syncUrlEnvKey)
if envOverride != "" {
return envOverride
}
if (globalConfig != nil) && (globalConfig.SyncApiUrl != "") {
return globalConfig.SyncApiUrl
}
return defaultSyncApiUrl
}
func ControlTowerUrl() string {
envOverride := os.Getenv(controlPlaneUrlEnvKey)
if envOverride != "" {
return envOverride
}
if (globalConfig != nil) && (globalConfig.ControlPlaneApiUrl != "") {
return globalConfig.ControlPlaneApiUrl
}
return defaultControlPlaneApiUrl
}
func InsightsApiV2Url() string {
envOverride := os.Getenv(apiV2UrlEnvKey)
if envOverride != "" {
return envOverride
}
if (globalConfig != nil) && (globalConfig.InsightsApiV2Url != "") {
return globalConfig.InsightsApiV2Url
}
return defaultInsightsApiV2Url
}
func CommunityServicesApiUrl() string {
envOverride := os.Getenv(communityServicesApiUrlEnvKey)
if envOverride != "" {
return envOverride
}
if (globalConfig != nil) && (globalConfig.CommunityServicesApiUrl != "") {
return globalConfig.CommunityServicesApiUrl
}
return defaultCommunityServicesApiUrl
}
func TenantDomain() string {
if tenantFromEnv, ok := os.LookupEnv(controlTowerTenantEnvKey); ok && tenantFromEnv != "" {
return tenantFromEnv
}
if tenantFromEnv, ok := os.LookupEnv(defaultSafeDepTenantIdEnvKey); ok && tenantFromEnv != "" {
return tenantFromEnv
}
if globalConfig != nil {
return globalConfig.TenantDomain
}
return ""
}
func ApiUrl() string {
if url, ok := os.LookupEnv(apiUrlEnvKey); ok {
return url
}
if CommunityMode() {
return defaultCommunityApiUrl
}
if globalConfig != nil {
return globalConfig.ApiUrl
}
return defaultApiUrl
}
func ApiKey() string {
if key, ok := os.LookupEnv(apiKeyEnvKey); ok {
return key
}
if key, ok := os.LookupEnv(apiKeyAlternateEnvKey); ok {
return key
}
if key, ok := os.LookupEnv(defaultSafeDepApiKeyEnvKey); ok {
return key
}
if globalConfig != nil {
return globalConfig.ApiKey
}
return ""
}
func CommunityMode() bool {
bRet, err := strconv.ParseBool(os.Getenv(communityModeEnvKey))
if (err == nil) && bRet {
return true
}
if globalConfig != nil {
return globalConfig.Community
}
return false
}
// SetRuntimeCommunityMode sets the runtime mode to community without
// persisting it to the configuration file.
func SetRuntimeCommunityMode() {
os.Setenv(communityModeEnvKey, "true")
}
func SetRuntimeCloudTenant(domain string) {
os.Setenv(controlTowerTenantEnvKey, domain)
}
func SetRuntimeApiKey(key string) {
os.Setenv(apiKeyEnvKey, key)
}
func loadConfiguration() error {
path, err := os.UserHomeDir()
if err != nil {
return err
}
path = filepath.Join(path, homeRelativeConfigPath)
data, err := os.ReadFile(path)
if err != nil {
return err
}
var config Config
err = yaml.Unmarshal(data, &config)
if err != nil {
return fmt.Errorf("config deserialization failed: %w", err)
}
globalConfig = &config
return nil
}
func persistConfiguration() error {
data, err := yaml.Marshal(globalConfig)
if err != nil {
return fmt.Errorf("config serialization failed: %w", err)
}
path, err := os.UserHomeDir()
if err != nil {
return err
}
path = filepath.Join(path, homeRelativeConfigPath)
_ = os.MkdirAll(filepath.Dir(path), os.ModePerm)
return os.WriteFile(path, data, 0o600)
}