From 8670263deb76e10b3031d9ef1a75395bd20538e4 Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 13:30:48 +0530 Subject: [PATCH 1/9] chore: styles --- phase/crypto/crypto.go | 191 ++++--- phase/misc/const.go | 57 +-- phase/misc/misc.go | 22 +- phase/network/network.go | 417 ++++++++------- phase/phase.go | 1052 +++++++++++++++++++------------------- 5 files changed, 868 insertions(+), 871 deletions(-) diff --git a/phase/crypto/crypto.go b/phase/crypto/crypto.go index 097cab1..77a6c30 100644 --- a/phase/crypto/crypto.go +++ b/phase/crypto/crypto.go @@ -122,19 +122,19 @@ func EncryptAsymmetric(plaintext, publicKeyHex string) (string, error) { return "", err } - // Spin up ephemeral X25519 keys + // Spin up ephemeral X25519 keys kp, err := RandomKeyPair() if err != nil { return "", err } - // Perform a DHKA + // Perform a DHKA sessionKeys, err := ClientSessionKeys(kp, recipientPubKeyBytes) if err != nil { return "", err } - // Encrypt data with XChaCha20-poly1305 + // Encrypt data with XChaCha20-poly1305 ciphertext, err := EncryptB64(plaintext, sessionKeys.Tx) if err != nil { return "", err @@ -145,93 +145,93 @@ func EncryptAsymmetric(plaintext, publicKeyHex string) (string, error) { } func DecryptAsymmetric(ciphertextString, privateKeyHex, publicKeyHex string) (string, error) { - segments := strings.Split(ciphertextString, ":") - - version := segments[1] - if version != "v1" { - err := fmt.Errorf("unsupported version: %s", version) - log.Println(err) - return "", err - } - - ephemeralPublicKeyBytes, err := hex.DecodeString(segments[2]) - if err != nil { - log.Printf("Failed to decode ephemeral public key hex: %v\n", err) - return "", err - } - - privateKeyBytes, err := hex.DecodeString(privateKeyHex) - if err != nil { - log.Printf("Failed to decode private key hex: %v\n", err) - return "", err - } - - publicKeyBytes, err := hex.DecodeString(publicKeyHex) - if err != nil { - log.Printf("Failed to decode public key hex: %v\n", err) - return "", err - } - - kp := sodium.KXKP{ - PublicKey: sodium.KXPublicKey{Bytes: publicKeyBytes}, - SecretKey: sodium.KXSecretKey{Bytes: privateKeyBytes}, - } - - // Perform DHKA - sessionKeys, err := ServerSessionKeys(kp, ephemeralPublicKeyBytes) - if err != nil { - return "", err - } - - // Extract ciphertext from ph. - ciphertextB64 := segments[3] - - // Decrypt data with XChaCha20-poly1305 - plaintext, err := DecryptB64(ciphertextB64, sessionKeys.Rx) - if err != nil { - log.Printf("Failed to decrypt asymmetrically: %v\n", err) - return "", err - } - return plaintext, nil + segments := strings.Split(ciphertextString, ":") + + version := segments[1] + if version != "v1" { + err := fmt.Errorf("unsupported version: %s", version) + log.Println(err) + return "", err + } + + ephemeralPublicKeyBytes, err := hex.DecodeString(segments[2]) + if err != nil { + log.Printf("Failed to decode ephemeral public key hex: %v\n", err) + return "", err + } + + privateKeyBytes, err := hex.DecodeString(privateKeyHex) + if err != nil { + log.Printf("Failed to decode private key hex: %v\n", err) + return "", err + } + + publicKeyBytes, err := hex.DecodeString(publicKeyHex) + if err != nil { + log.Printf("Failed to decode public key hex: %v\n", err) + return "", err + } + + kp := sodium.KXKP{ + PublicKey: sodium.KXPublicKey{Bytes: publicKeyBytes}, + SecretKey: sodium.KXSecretKey{Bytes: privateKeyBytes}, + } + + // Perform DHKA + sessionKeys, err := ServerSessionKeys(kp, ephemeralPublicKeyBytes) + if err != nil { + return "", err + } + + // Extract ciphertext from ph. + ciphertextB64 := segments[3] + + // Decrypt data with XChaCha20-poly1305 + plaintext, err := DecryptB64(ciphertextB64, sessionKeys.Rx) + if err != nil { + log.Printf("Failed to decrypt asymmetrically: %v\n", err) + return "", err + } + return plaintext, nil } // decryptSecret decrypts a secret's key, value, and optional comment using asymmetric decryption. func DecryptSecret(secret map[string]interface{}, privateKeyHex, publicKeyHex string) (decryptedKey string, decryptedValue string, decryptedComment string, err error) { - // Decrypt the key - key, ok := secret["key"].(string) - if !ok { - err = fmt.Errorf("key is not a string") - return - } - decryptedKey, err = DecryptAsymmetric(key, privateKeyHex, publicKeyHex) - if err != nil { - log.Printf("Failed to decrypt key: %v\n", err) - return - } - - // Decrypt the value - value, ok := secret["value"].(string) - if !ok { - err = fmt.Errorf("value is not a string") - return - } - decryptedValue, err = DecryptAsymmetric(value, privateKeyHex, publicKeyHex) - if err != nil { - log.Printf("Failed to decrypt value: %v\n", err) - return - } - - // Decrypt the comment if it exists - comment, ok := secret["comment"].(string) - if ok && comment != "" { - decryptedComment, err = DecryptAsymmetric(comment, privateKeyHex, publicKeyHex) - if err != nil { - log.Printf("Failed to decrypt comment: %v\n", err) - err = nil - } - } - - return decryptedKey, decryptedValue, decryptedComment, nil + // Decrypt the key + key, ok := secret["key"].(string) + if !ok { + err = fmt.Errorf("key is not a string") + return + } + decryptedKey, err = DecryptAsymmetric(key, privateKeyHex, publicKeyHex) + if err != nil { + log.Printf("Failed to decrypt key: %v\n", err) + return + } + + // Decrypt the value + value, ok := secret["value"].(string) + if !ok { + err = fmt.Errorf("value is not a string") + return + } + decryptedValue, err = DecryptAsymmetric(value, privateKeyHex, publicKeyHex) + if err != nil { + log.Printf("Failed to decrypt value: %v\n", err) + return + } + + // Decrypt the comment if it exists + comment, ok := secret["comment"].(string) + if ok && comment != "" { + decryptedComment, err = DecryptAsymmetric(comment, privateKeyHex, publicKeyHex) + if err != nil { + log.Printf("Failed to decrypt comment: %v\n", err) + err = nil + } + } + + return decryptedKey, decryptedValue, decryptedComment, nil } // Decrypt decrypts the provided ciphertext using the Phase encryption mechanism. @@ -293,24 +293,23 @@ func GenerateEnvKeyPair(seed string) (publicKeyHex, privateKeyHex string, err er return "", "", fmt.Errorf("incorrect seed length: expected 32 bytes, got %d", len(seedBytes)) } - // Prepare the seed as KXSeed - var seedKX sodium.KXSeed - copy(seedKX.Bytes[:], seedBytes) + // Prepare the seed as KXSeed + var seedKX sodium.KXSeed + copy(seedKX.Bytes[:], seedBytes) // Allocate slice if KXSeed.Bytes is a slice seedKX.Bytes = make([]byte, len(seedBytes)) copy(seedKX.Bytes, seedBytes) - // Generate key pair from seed - keyPair := sodium.SeedKXKP(seedKX) + // Generate key pair from seed + keyPair := sodium.SeedKXKP(seedKX) - publicKeyHex = hex.EncodeToString(keyPair.PublicKey.Bytes[:]) - privateKeyHex = hex.EncodeToString(keyPair.SecretKey.Bytes[:]) + publicKeyHex = hex.EncodeToString(keyPair.PublicKey.Bytes[:]) + privateKeyHex = hex.EncodeToString(keyPair.SecretKey.Bytes[:]) - return publicKeyHex, privateKeyHex, nil + return publicKeyHex, privateKeyHex, nil } - // Blake2bDigest generates a BLAKE2b hash of the input string with a salt using the sodium library. func Blake2bDigest(inputStr, salt string) (string, error) { hashSize := 32 // 32 bytes (256 bits) as an example @@ -370,4 +369,4 @@ func ReconstructSecret(share1, share2 string) (string, error) { // Encode the reconstructed secret back to a hex string. return hex.EncodeToString(reconstructedSecret), nil -} \ No newline at end of file +} diff --git a/phase/misc/const.go b/phase/misc/const.go index 0abfea4..56f3e15 100644 --- a/phase/misc/const.go +++ b/phase/misc/const.go @@ -5,14 +5,14 @@ import ( ) const ( - Version = "1.0.1" - PhVersion = "v1" + Version = "1.0.1" + PhVersion = "v1" PhaseCloudAPIHost = "https://console.phase.dev" ) var ( - VerifySSL = false - PhaseDebug = false + VerifySSL = false + PhaseDebug = false ) var ( @@ -20,43 +20,42 @@ var ( // Compiled regex patterns PssUserPattern = regexp.MustCompile(`^pss_user:v(\d+):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64})$`) PssServicePattern = regexp.MustCompile(`^pss_service:v(\d+):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64})$`) - - // CrossEnvPattern = regexp.MustCompile(`\$\{(.+?)\.(.+?)\}`) + + // CrossEnvPattern = regexp.MustCompile(`\$\{(.+?)\.(.+?)\}`) // LocalRefPattern = regexp.MustCompile(`\$\{([^.]+?)\}`) - - // Regex to identify secret references - SecretRefRegex = regexp.MustCompile(`\$\{([^}]+)\}`) -) + // Regex to identify secret references + SecretRefRegex = regexp.MustCompile(`\$\{([^}]+)\}`) +) type Environment struct { - ID string `json:"id"` - Name string `json:"name"` - EnvType string `json:"env_type"` + ID string `json:"id"` + Name string `json:"name"` + EnvType string `json:"env_type"` } type EnvironmentKey struct { - ID string `json:"id"` - Environment Environment `json:"environment"` - IdentityKey string `json:"identity_key"` - WrappedSeed string `json:"wrapped_seed"` - WrappedSalt string `json:"wrapped_salt"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - DeletedAt *string `json:"deleted_at"` - User *string `json:"user"` + ID string `json:"id"` + Environment Environment `json:"environment"` + IdentityKey string `json:"identity_key"` + WrappedSeed string `json:"wrapped_seed"` + WrappedSalt string `json:"wrapped_salt"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + DeletedAt *string `json:"deleted_at"` + User *string `json:"user"` } type App struct { - ID string `json:"id"` - Name string `json:"name"` - Encryption string `json:"encryption"` - EnvironmentKeys []EnvironmentKey `json:"environment_keys"` + ID string `json:"id"` + Name string `json:"name"` + Encryption string `json:"encryption"` + EnvironmentKeys []EnvironmentKey `json:"environment_keys"` } type AppKeyResponse struct { - WrappedKeyShare string `json:"wrapped_key_share"` - Apps []App `json:"apps"` + WrappedKeyShare string `json:"wrapped_key_share"` + Apps []App `json:"apps"` } type GetContextOptions struct { @@ -69,4 +68,4 @@ type FindEnvironmentKeyOptions struct { EnvName string AppName string AppID string -} \ No newline at end of file +} diff --git a/phase/misc/misc.go b/phase/misc/misc.go index bfa2e1a..0ed88d7 100644 --- a/phase/misc/misc.go +++ b/phase/misc/misc.go @@ -25,8 +25,8 @@ func FindEnvironmentKey(userData AppKeyResponse, opts FindEnvironmentKeyOptions) lcAppName := strings.ToLower(opts.AppName) for _, app := range userData.Apps { - if (opts.AppID != "" && app.ID == opts.AppID) || - (opts.AppName != "" && (opts.AppName == "" || strings.Contains(strings.ToLower(app.Name), lcAppName))) { + if (opts.AppID != "" && app.ID == opts.AppID) || + (opts.AppName != "" && (opts.AppName == "" || strings.Contains(strings.ToLower(app.Name), lcAppName))) { for _, envKey := range app.EnvironmentKeys { if strings.Contains(strings.ToLower(envKey.Environment.Name), lcEnvName) { return &envKey, nil @@ -39,17 +39,17 @@ func FindEnvironmentKey(userData AppKeyResponse, opts FindEnvironmentKeyOptions) // normalizeTag replaces underscores with spaces and converts the string to lower case. func normalizeTag(tag string) string { - return strings.ToLower(strings.Replace(tag, "_", " ", -1)) + return strings.ToLower(strings.Replace(tag, "_", " ", -1)) } // tagMatches checks if the user-provided tag partially matches any of the secret tags. func TagMatches(secretTags []string, userTag string) bool { - normalizedUserTag := normalizeTag(userTag) - for _, tag := range secretTags { - normalizedSecretTag := normalizeTag(tag) - if strings.Contains(normalizedSecretTag, normalizedUserTag) { - return true - } - } - return false + normalizedUserTag := normalizeTag(userTag) + for _, tag := range secretTags { + normalizedSecretTag := normalizeTag(tag) + if strings.Contains(normalizedSecretTag, normalizedUserTag) { + return true + } + } + return false } diff --git a/phase/network/network.go b/phase/network/network.go index 60f8382..aaf04fc 100644 --- a/phase/network/network.go +++ b/phase/network/network.go @@ -17,11 +17,11 @@ import ( ) func ConstructHTTPHeaders(appToken string) http.Header { - headers := http.Header{} - // Adjusting to use "Bearer Service" as part of the token value - headers.Set("Authorization", fmt.Sprintf("Bearer Service %s", appToken)) - headers.Set("User-Agent", GetUserAgent()) - return headers + headers := http.Header{} + // Adjusting to use "Bearer Service" as part of the token value + headers.Set("Authorization", fmt.Sprintf("Bearer Service %s", appToken)) + headers.Set("User-Agent", GetUserAgent()) + return headers } var customUserAgent string @@ -53,18 +53,18 @@ func GetUserAgent() string { } } - // Return only the concatenated string without "User-Agent:" prefix + // Return only the concatenated string without "User-Agent:" prefix return strings.Join(details, " ") } func createHTTPClient() *http.Client { - client := &http.Client{} - if !misc.VerifySSL { - client.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - } - return client + client := &http.Client{} + if !misc.VerifySSL { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } + return client } // handleHTTPResponse checks the HTTP response status and handles errors appropriately. @@ -94,221 +94,220 @@ func handleHTTPResponse(resp *http.Response) error { } func FetchPhaseUser(appToken, host string) (*http.Response, error) { - client := createHTTPClient() - url := fmt.Sprintf("%s/service/secrets/tokens/", host) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header = ConstructHTTPHeaders(appToken) - resp, err := client.Do(req) - if err != nil { - return nil, err - } - err = handleHTTPResponse(resp) - if err != nil { - resp.Body.Close() // Ensure response body is closed on error - return nil, err - } - - return resp, nil + client := createHTTPClient() + url := fmt.Sprintf("%s/service/secrets/tokens/", host) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + req.Header = ConstructHTTPHeaders(appToken) + resp, err := client.Do(req) + if err != nil { + return nil, err + } + err = handleHTTPResponse(resp) + if err != nil { + resp.Body.Close() // Ensure response body is closed on error + return nil, err + } + + return resp, nil } type AppKeyResponse struct { - WrappedKeyShare string `json:"wrapped_key_share"` - Apps []struct { - ID string `json:"id"` - Name string `json:"name"` - Encryption string `json:"encryption"` - EnvironmentKeys []struct { - ID string `json:"id"` - Environment struct { - ID string `json:"id"` - Name string `json:"name"` - EnvType string `json:"env_type"` - } `json:"environment"` - IdentityKey string `json:"identity_key"` - WrappedSeed string `json:"wrapped_seed"` - WrappedSalt string `json:"wrapped_salt"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - DeletedAt *string `json:"deleted_at"` // Use pointer to accommodate null - User *string `json:"user"` // Use pointer to accommodate null - } `json:"environment_keys"` - } `json:"apps"` + WrappedKeyShare string `json:"wrapped_key_share"` + Apps []struct { + ID string `json:"id"` + Name string `json:"name"` + Encryption string `json:"encryption"` + EnvironmentKeys []struct { + ID string `json:"id"` + Environment struct { + ID string `json:"id"` + Name string `json:"name"` + EnvType string `json:"env_type"` + } `json:"environment"` + IdentityKey string `json:"identity_key"` + WrappedSeed string `json:"wrapped_seed"` + WrappedSalt string `json:"wrapped_salt"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + DeletedAt *string `json:"deleted_at"` // Use pointer to accommodate null + User *string `json:"user"` // Use pointer to accommodate null + } `json:"environment_keys"` + } `json:"apps"` } func FetchAppKey(appToken, host string) (string, error) { - client := createHTTPClient() - url := fmt.Sprintf("%s/service/secrets/tokens/", host) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return "", err - } - - req.Header = ConstructHTTPHeaders(appToken) - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - if err := handleHTTPResponse(resp); err != nil { - return "", err - } - - var jsonResp AppKeyResponse - if err := json.NewDecoder(resp.Body).Decode(&jsonResp); err != nil { - return "", fmt.Errorf("failed to decode JSON: %v", err) - } - - return jsonResp.WrappedKeyShare, nil + client := createHTTPClient() + url := fmt.Sprintf("%s/service/secrets/tokens/", host) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", err + } + + req.Header = ConstructHTTPHeaders(appToken) + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if err := handleHTTPResponse(resp); err != nil { + return "", err + } + + var jsonResp AppKeyResponse + if err := json.NewDecoder(resp.Body).Decode(&jsonResp); err != nil { + return "", fmt.Errorf("failed to decode JSON: %v", err) + } + + return jsonResp.WrappedKeyShare, nil } func FetchPhaseSecrets(appToken, environmentID, host, path string) ([]map[string]interface{}, error) { - client := createHTTPClient() - url := fmt.Sprintf("%s/service/secrets/", host) - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header = ConstructHTTPHeaders(appToken) - req.Header.Set("Environment", environmentID) - if path != "" { - req.Header.Set("Path", path) - } - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if err := handleHTTPResponse(resp); err != nil { - return nil, err - } - - var secrets []map[string]interface{} - if err := json.NewDecoder(resp.Body).Decode(&secrets); err != nil { - return nil, fmt.Errorf("failed to decode JSON response: %v", err) - } - - return secrets, nil + client := createHTTPClient() + url := fmt.Sprintf("%s/service/secrets/", host) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + req.Header = ConstructHTTPHeaders(appToken) + req.Header.Set("Environment", environmentID) + if path != "" { + req.Header.Set("Path", path) + } + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err := handleHTTPResponse(resp); err != nil { + return nil, err + } + + var secrets []map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&secrets); err != nil { + return nil, fmt.Errorf("failed to decode JSON response: %v", err) + } + + return secrets, nil } func FetchPhaseSecret(appToken, environmentID, host, keyDigest, path string) (map[string]interface{}, error) { - client := createHTTPClient() - url := fmt.Sprintf("%s/service/secrets/", host) - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header = ConstructHTTPHeaders(appToken) - req.Header.Set("Environment", environmentID) - req.Header.Set("KeyDigest", keyDigest) - if path != "" { - req.Header.Set("Path", path) - } - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if err := handleHTTPResponse(resp); err != nil { - return nil, err - } - - var secrets []map[string]interface{} - if err := json.NewDecoder(resp.Body).Decode(&secrets); err != nil { - return nil, fmt.Errorf("failed to decode JSON response: %v", err) - } - - if len(secrets) > 0 { - return secrets[0], nil - } - - return nil, fmt.Errorf("no secrets found in the response") + client := createHTTPClient() + url := fmt.Sprintf("%s/service/secrets/", host) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + req.Header = ConstructHTTPHeaders(appToken) + req.Header.Set("Environment", environmentID) + req.Header.Set("KeyDigest", keyDigest) + if path != "" { + req.Header.Set("Path", path) + } + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err := handleHTTPResponse(resp); err != nil { + return nil, err + } + + var secrets []map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&secrets); err != nil { + return nil, fmt.Errorf("failed to decode JSON response: %v", err) + } + + if len(secrets) > 0 { + return secrets[0], nil + } + + return nil, fmt.Errorf("no secrets found in the response") } func CreatePhaseSecrets(appToken, environmentID string, secrets []map[string]interface{}, host string) error { - client := createHTTPClient() - url := fmt.Sprintf("%s/service/secrets/", host) - data, err := json.Marshal(map[string][]map[string]interface{}{"secrets": secrets}) - if err != nil { - return err - } - - req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) - if err != nil { - return err - } - - req.Header = ConstructHTTPHeaders(appToken) - req.Header.Set("Environment", environmentID) - - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - return handleHTTPResponse(resp) + client := createHTTPClient() + url := fmt.Sprintf("%s/service/secrets/", host) + data, err := json.Marshal(map[string][]map[string]interface{}{"secrets": secrets}) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) + if err != nil { + return err + } + + req.Header = ConstructHTTPHeaders(appToken) + req.Header.Set("Environment", environmentID) + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + return handleHTTPResponse(resp) } func UpdatePhaseSecrets(appToken, environmentID string, secrets []map[string]interface{}, host string) error { - client := createHTTPClient() - url := fmt.Sprintf("%s/service/secrets/", host) - data, err := json.Marshal(map[string][]map[string]interface{}{"secrets": secrets}) - if err != nil { - return err - } - - req, err := http.NewRequest("PUT", url, bytes.NewBuffer(data)) - if err != nil { - return err - } - - req.Header = ConstructHTTPHeaders(appToken) - req.Header.Set("Environment", environmentID) - - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - return handleHTTPResponse(resp) + client := createHTTPClient() + url := fmt.Sprintf("%s/service/secrets/", host) + data, err := json.Marshal(map[string][]map[string]interface{}{"secrets": secrets}) + if err != nil { + return err + } + + req, err := http.NewRequest("PUT", url, bytes.NewBuffer(data)) + if err != nil { + return err + } + + req.Header = ConstructHTTPHeaders(appToken) + req.Header.Set("Environment", environmentID) + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + return handleHTTPResponse(resp) } func DeletePhaseSecrets(appToken, environmentID string, secretIDs []string, host string) error { - client := createHTTPClient() - url := fmt.Sprintf("%s/service/secrets/", host) - data, err := json.Marshal(map[string][]string{"secrets": secretIDs}) - if err != nil { - return err - } - - req, err := http.NewRequest("DELETE", url, bytes.NewBuffer(data)) - if err != nil { - return err - } - - req.Header = ConstructHTTPHeaders(appToken) - req.Header.Set("Environment", environmentID) - - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - return handleHTTPResponse(resp) -} + client := createHTTPClient() + url := fmt.Sprintf("%s/service/secrets/", host) + data, err := json.Marshal(map[string][]string{"secrets": secretIDs}) + if err != nil { + return err + } + + req, err := http.NewRequest("DELETE", url, bytes.NewBuffer(data)) + if err != nil { + return err + } + + req.Header = ConstructHTTPHeaders(appToken) + req.Header.Set("Environment", environmentID) + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + return handleHTTPResponse(resp) +} diff --git a/phase/phase.go b/phase/phase.go index 1b6f7b9..8722bf1 100644 --- a/phase/phase.go +++ b/phase/phase.go @@ -13,14 +13,14 @@ import ( // Phase struct to hold parsed service token information and host. type Phase struct { - Prefix string - PesVersion string - AppToken string - PssUserPublicKey string - Keyshare0 string - Keyshare1UnwrapKey string - Host string - Debug bool + Prefix string + PesVersion string + AppToken string + PssUserPublicKey string + Keyshare0 string + Keyshare1UnwrapKey string + Host string + Debug bool } type GetSecretOptions struct { @@ -67,107 +67,107 @@ type DeleteSecretOptions struct { // Init initializes a new instance of Phase with the provided service token, host, and debug flag. func Init(serviceToken, host string, debug bool) *Phase { - // Validate the service token against the pattern. - matches := misc.PssServicePattern.FindStringSubmatch(serviceToken) - if matches == nil || len(matches) != 6 { - log.Fatalf("Error: Invalid Phase Service Token.") - } - - // Use default host if none is specified. - if host == "" { - host = misc.PhaseCloudAPIHost - } - - // Create a new Phase instance with parsed service token components and debug flag. - return &Phase{ - Prefix: "pss_service", - PesVersion: matches[1], - AppToken: matches[2], - PssUserPublicKey: matches[3], - Keyshare0: matches[4], - Keyshare1UnwrapKey: matches[5], - Host: host, - Debug: debug, - } + // Validate the service token against the pattern. + matches := misc.PssServicePattern.FindStringSubmatch(serviceToken) + if matches == nil || len(matches) != 6 { + log.Fatalf("Error: Invalid Phase Service Token.") + } + + // Use default host if none is specified. + if host == "" { + host = misc.PhaseCloudAPIHost + } + + // Create a new Phase instance with parsed service token components and debug flag. + return &Phase{ + Prefix: "pss_service", + PesVersion: matches[1], + AppToken: matches[2], + PssUserPublicKey: matches[3], + Keyshare0: matches[4], + Keyshare1UnwrapKey: matches[5], + Host: host, + Debug: debug, + } } func (p *Phase) resolveSecretReference(ref, currentEnvName string) (string, error) { - var envName, path, keyName string - - // Check if the reference starts with an environment name followed by a dot - if strings.Contains(ref, ".") { - // Split on the first dot to differentiate environment from path/key - parts := strings.SplitN(ref, ".", 2) - envName = parts[0] - - // Further split the second part to separate the path and the key - // The last segment after the last "/" is the key, the rest is the path - lastSlashIndex := strings.LastIndex(parts[1], "/") - if lastSlashIndex != -1 { // Path is specified - path = parts[1][:lastSlashIndex] // Include the slash in the path - keyName = parts[1][lastSlashIndex+1:] - } else { // No path specified, use root - path = "/" - keyName = parts[1] - } - } else { // Local reference without an environment prefix - envName = currentEnvName - lastSlashIndex := strings.LastIndex(ref, "/") - if lastSlashIndex != -1 { // Path is specified - path = ref[:lastSlashIndex] // Include the slash in the path - keyName = ref[lastSlashIndex+1:] - } else { // No path specified, use root - path = "/" - keyName = ref - } - } - - // Validate the extracted parts - if keyName == "" { - return "", fmt.Errorf("invalid secret reference format: %s", ref) - } - - // Fetch and decrypt the referenced secret - opts := GetSecretOptions{ - EnvName: envName, - AppName: "", // AppName is available globally - KeyToFind: keyName, - SecretPath: path, - } - resolvedSecret, err := p.Get(opts) - if err != nil { - return "", fmt.Errorf("failed to resolve secret reference %s: %v", ref, err) - } - - // Return the decrypted value of the referenced secret - decryptedValue, ok := (*resolvedSecret)["value"].(string) - if !ok { - return "", fmt.Errorf("decrypted value of the secret reference %s is not a string", ref) - } - - return decryptedValue, nil + var envName, path, keyName string + + // Check if the reference starts with an environment name followed by a dot + if strings.Contains(ref, ".") { + // Split on the first dot to differentiate environment from path/key + parts := strings.SplitN(ref, ".", 2) + envName = parts[0] + + // Further split the second part to separate the path and the key + // The last segment after the last "/" is the key, the rest is the path + lastSlashIndex := strings.LastIndex(parts[1], "/") + if lastSlashIndex != -1 { // Path is specified + path = parts[1][:lastSlashIndex] // Include the slash in the path + keyName = parts[1][lastSlashIndex+1:] + } else { // No path specified, use root + path = "/" + keyName = parts[1] + } + } else { // Local reference without an environment prefix + envName = currentEnvName + lastSlashIndex := strings.LastIndex(ref, "/") + if lastSlashIndex != -1 { // Path is specified + path = ref[:lastSlashIndex] // Include the slash in the path + keyName = ref[lastSlashIndex+1:] + } else { // No path specified, use root + path = "/" + keyName = ref + } + } + + // Validate the extracted parts + if keyName == "" { + return "", fmt.Errorf("invalid secret reference format: %s", ref) + } + + // Fetch and decrypt the referenced secret + opts := GetSecretOptions{ + EnvName: envName, + AppName: "", // AppName is available globally + KeyToFind: keyName, + SecretPath: path, + } + resolvedSecret, err := p.Get(opts) + if err != nil { + return "", fmt.Errorf("failed to resolve secret reference %s: %v", ref, err) + } + + // Return the decrypted value of the referenced secret + decryptedValue, ok := (*resolvedSecret)["value"].(string) + if !ok { + return "", fmt.Errorf("decrypted value of the secret reference %s is not a string", ref) + } + + return decryptedValue, nil } // resolveSecretValue resolves all secret references in a given value string. func (p *Phase) resolveSecretValue(value string, currentEnvName string) (string, error) { - refs := misc.SecretRefRegex.FindAllString(value, -1) - resolvedValue := value - - for _, ref := range refs { - // Extract just the reference part without the surrounding ${} - refMatch := misc.SecretRefRegex.FindStringSubmatch(ref) - if len(refMatch) > 1 { - // Pass the current environment name if needed for resolution - resolvedSecretValue, err := p.resolveSecretReference(refMatch[1], currentEnvName) - if err != nil { - return "", err - } - // Directly use the string value returned by resolveSecretReference - resolvedValue = strings.Replace(resolvedValue, ref, resolvedSecretValue, -1) - } - } - - return resolvedValue, nil + refs := misc.SecretRefRegex.FindAllString(value, -1) + resolvedValue := value + + for _, ref := range refs { + // Extract just the reference part without the surrounding ${} + refMatch := misc.SecretRefRegex.FindStringSubmatch(ref) + if len(refMatch) > 1 { + // Pass the current environment name if needed for resolution + resolvedSecretValue, err := p.resolveSecretReference(refMatch[1], currentEnvName) + if err != nil { + return "", err + } + // Directly use the string value returned by resolveSecretReference + resolvedValue = strings.Replace(resolvedValue, ref, resolvedSecretValue, -1) + } + } + + return resolvedValue, nil } // Get fetches and decrypts a secret, resolving any secret references within its value. @@ -202,114 +202,114 @@ func (p *Phase) Get(opts GetSecretOptions) (*map[string]interface{}, error) { return nil, err } - decryptedSeed, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSeed, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to decrypt wrapped seed: %v", err) - } - return nil, err - } - decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to decrypt wrapped salt: %v", err) - } - return nil, err - } - - publicKeyHex, privateKeyHex, err := crypto.GenerateEnvKeyPair(decryptedSeed) - if err != nil { - if p.Debug { - log.Printf("Failed to generate environment key pair: %v", err) - } - return nil, err - } - - keyDigest, err := crypto.Blake2bDigest(opts.KeyToFind, decryptedSalt) - if err != nil { - if p.Debug { - log.Printf("Failed to generate key digest: %v", err) - } - return nil, err - } - - // Fetch a single secret based on keyDigest and optional path - secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) - if err != nil { - if p.Debug { - log.Printf("Failed to fetch secret: %v", err) - } - return nil, err - } - - decryptedKey, decryptedValue, decryptedComment, err := crypto.DecryptSecret(secret, privateKeyHex, publicKeyHex) - if err != nil { - if p.Debug { - log.Printf("Failed to decrypt secret: %v", err) - } - return nil, err - } - - // Resolve any secret references within the decryptedValue before creating the result map - resolvedValue, err := p.resolveSecretValue(decryptedValue, opts.EnvName) - if err != nil { - if p.Debug { - log.Printf("Failed to resolve secret value: %v", err) - } - return nil, err - } - - // Verify tag match if a tag is provided - var stringTags []string - if opts.Tag != "" { - if secretTags, ok := secret["tags"].([]interface{}); ok { - for _, tagInterface := range secretTags { - if tagStr, ok := tagInterface.(string); ok { - stringTags = append(stringTags, tagStr) - } - } - if !misc.TagMatches(stringTags, opts.Tag) { - return nil, fmt.Errorf("secret with key '%s' found, but doesn't match the provided tag '%s'", opts.KeyToFind, opts.Tag) - } - } - } - - // Extract the path directly from the secret map - secretPath, _ := secret["path"].(string) - - result := &map[string]interface{}{ - "key": decryptedKey, - "value": resolvedValue, // Use resolvedValue here - "comment": decryptedComment, - "tags": stringTags, - "path": secretPath, - } - - if p.Debug { - log.Println("Secret fetched successfully") - } - - return result, nil + decryptedSeed, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSeed, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to decrypt wrapped seed: %v", err) + } + return nil, err + } + decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to decrypt wrapped salt: %v", err) + } + return nil, err + } + + publicKeyHex, privateKeyHex, err := crypto.GenerateEnvKeyPair(decryptedSeed) + if err != nil { + if p.Debug { + log.Printf("Failed to generate environment key pair: %v", err) + } + return nil, err + } + + keyDigest, err := crypto.Blake2bDigest(opts.KeyToFind, decryptedSalt) + if err != nil { + if p.Debug { + log.Printf("Failed to generate key digest: %v", err) + } + return nil, err + } + + // Fetch a single secret based on keyDigest and optional path + secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) + if err != nil { + if p.Debug { + log.Printf("Failed to fetch secret: %v", err) + } + return nil, err + } + + decryptedKey, decryptedValue, decryptedComment, err := crypto.DecryptSecret(secret, privateKeyHex, publicKeyHex) + if err != nil { + if p.Debug { + log.Printf("Failed to decrypt secret: %v", err) + } + return nil, err + } + + // Resolve any secret references within the decryptedValue before creating the result map + resolvedValue, err := p.resolveSecretValue(decryptedValue, opts.EnvName) + if err != nil { + if p.Debug { + log.Printf("Failed to resolve secret value: %v", err) + } + return nil, err + } + + // Verify tag match if a tag is provided + var stringTags []string + if opts.Tag != "" { + if secretTags, ok := secret["tags"].([]interface{}); ok { + for _, tagInterface := range secretTags { + if tagStr, ok := tagInterface.(string); ok { + stringTags = append(stringTags, tagStr) + } + } + if !misc.TagMatches(stringTags, opts.Tag) { + return nil, fmt.Errorf("secret with key '%s' found, but doesn't match the provided tag '%s'", opts.KeyToFind, opts.Tag) + } + } + } + + // Extract the path directly from the secret map + secretPath, _ := secret["path"].(string) + + result := &map[string]interface{}{ + "key": decryptedKey, + "value": resolvedValue, // Use resolvedValue here + "comment": decryptedComment, + "tags": stringTags, + "path": secretPath, + } + + if p.Debug { + log.Println("Secret fetched successfully") + } + + return result, nil } func (p *Phase) GetAll(opts GetAllSecretsOptions) ([]map[string]interface{}, error) { - // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to fetch user data: %v", err) - } - return nil, err - } - defer resp.Body.Close() - - var userData misc.AppKeyResponse - if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { - if p.Debug { - log.Printf("Failed to decode user data: %v", err) - } - return nil, err - } + // Fetch user data + resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to fetch user data: %v", err) + } + return nil, err + } + defer resp.Body.Close() + + var userData misc.AppKeyResponse + if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { + if p.Debug { + log.Printf("Failed to decode user data: %v", err) + } + return nil, err + } envKey, err := misc.FindEnvironmentKey(userData, misc.FindEnvironmentKeyOptions{ EnvName: opts.EnvName, @@ -323,109 +323,109 @@ func (p *Phase) GetAll(opts GetAllSecretsOptions) ([]map[string]interface{}, err return nil, err } - // Decrypt the wrapped seed - decryptedSeed, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSeed, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to decrypt wrapped seed: %v", err) - } - return nil, err - } - - // Generate environment key pair - publicKeyHex, privateKeyHex, err := crypto.GenerateEnvKeyPair(decryptedSeed) - if err != nil { - if p.Debug { - log.Printf("Failed to generate environment key pair: %v", err) - } - return nil, err - } - - // Fetch secrets with optional path filtering - secrets, err := network.FetchPhaseSecrets(p.AppToken, envKey.Environment.ID, p.Host, opts.SecretPath) - if err != nil { - if p.Debug { - log.Printf("Failed to fetch secrets: %v", err) - } - return nil, err - } - - decryptedSecrets := make([]map[string]interface{}, 0) - for _, secret := range secrets { - // Decrypt key, value, and optional comment - decryptedKey, decryptedValue, decryptedComment, err := crypto.DecryptSecret(secret, privateKeyHex, publicKeyHex) - if err != nil { - if p.Debug { - log.Printf("Failed to decrypt secret: %v\n", err) - } - continue - } - - // Resolve any secret references within the decryptedValue - resolvedValue, err := p.resolveSecretValue(decryptedValue, opts.EnvName) - if err != nil { - if p.Debug { - log.Printf("Failed to resolve secret value: %v\n", err) - } - continue - } - - // Prepare tags for inclusion in result - var stringTags []string - if secretTags, ok := secret["tags"].([]interface{}); ok { - for _, tagInterface := range secretTags { - if tagStr, ok := tagInterface.(string); ok { - stringTags = append(stringTags, tagStr) - } - } - } - - // Check for tag match if a tag is provided - if opts.Tag != "" && !misc.TagMatches(stringTags, opts.Tag) { - continue - } - - // Extract path directly from the secret map - path, _ := secret["path"].(string) - - // Append decrypted secret with path to result list - result := map[string]interface{}{ - "key": decryptedKey, - "value": resolvedValue, // Use resolvedValue here - "comment": decryptedComment, - "tags": stringTags, - "path": path, - } - - decryptedSecrets = append(decryptedSecrets, result) - } - - if p.Debug { - log.Println("Secrets fetched successfully") - } - - return decryptedSecrets, nil + // Decrypt the wrapped seed + decryptedSeed, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSeed, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to decrypt wrapped seed: %v", err) + } + return nil, err + } + + // Generate environment key pair + publicKeyHex, privateKeyHex, err := crypto.GenerateEnvKeyPair(decryptedSeed) + if err != nil { + if p.Debug { + log.Printf("Failed to generate environment key pair: %v", err) + } + return nil, err + } + + // Fetch secrets with optional path filtering + secrets, err := network.FetchPhaseSecrets(p.AppToken, envKey.Environment.ID, p.Host, opts.SecretPath) + if err != nil { + if p.Debug { + log.Printf("Failed to fetch secrets: %v", err) + } + return nil, err + } + + decryptedSecrets := make([]map[string]interface{}, 0) + for _, secret := range secrets { + // Decrypt key, value, and optional comment + decryptedKey, decryptedValue, decryptedComment, err := crypto.DecryptSecret(secret, privateKeyHex, publicKeyHex) + if err != nil { + if p.Debug { + log.Printf("Failed to decrypt secret: %v\n", err) + } + continue + } + + // Resolve any secret references within the decryptedValue + resolvedValue, err := p.resolveSecretValue(decryptedValue, opts.EnvName) + if err != nil { + if p.Debug { + log.Printf("Failed to resolve secret value: %v\n", err) + } + continue + } + + // Prepare tags for inclusion in result + var stringTags []string + if secretTags, ok := secret["tags"].([]interface{}); ok { + for _, tagInterface := range secretTags { + if tagStr, ok := tagInterface.(string); ok { + stringTags = append(stringTags, tagStr) + } + } + } + + // Check for tag match if a tag is provided + if opts.Tag != "" && !misc.TagMatches(stringTags, opts.Tag) { + continue + } + + // Extract path directly from the secret map + path, _ := secret["path"].(string) + + // Append decrypted secret with path to result list + result := map[string]interface{}{ + "key": decryptedKey, + "value": resolvedValue, // Use resolvedValue here + "comment": decryptedComment, + "tags": stringTags, + "path": path, + } + + decryptedSecrets = append(decryptedSecrets, result) + } + + if p.Debug { + log.Println("Secrets fetched successfully") + } + + return decryptedSecrets, nil } // CreateSecrets creates new secrets in the Phase KMS for the specified environment and application. func (p *Phase) Create(opts CreateSecretsOptions) error { - // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to fetch user data: %v", err) - } - return err - } - defer resp.Body.Close() - - var userData misc.AppKeyResponse - if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { - if p.Debug { - log.Printf("Failed to decode user data: %v", err) - } - return err - } + // Fetch user data + resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to fetch user data: %v", err) + } + return err + } + defer resp.Body.Close() + + var userData misc.AppKeyResponse + if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { + if p.Debug { + log.Printf("Failed to decode user data: %v", err) + } + return err + } _, envID, publicKey, err := misc.PhaseGetContext(userData, misc.GetContextOptions{ AppName: opts.AppName, @@ -451,91 +451,91 @@ func (p *Phase) Create(opts CreateSecretsOptions) error { return err } - decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to decrypt wrapped salt: %v", err) - } - return err - } - - secrets := make([]map[string]interface{}, 0) - for _, pair := range opts.KeyValuePairs { - for key, value := range pair { - encryptedKey, err := crypto.EncryptAsymmetric(key, publicKey) - if err != nil { - if p.Debug { - log.Printf("Failed to encrypt key: %v\n", err) - } - continue - } - - encryptedValue, err := crypto.EncryptAsymmetric(value, publicKey) - if err != nil { - if p.Debug { - log.Printf("Failed to encrypt value: %v\n", err) - } - continue - } - - keyDigest, err := crypto.Blake2bDigest(key, decryptedSalt) - if err != nil { - if p.Debug { - log.Printf("Failed to generate key digest: %v\n", err) - } - continue - } - - // Determine the path for the secret, default to "/" if not specified - path, ok := opts.SecretPath[key] - if !ok { - path = "/" // Default path if not provided - } - - secret := map[string]interface{}{ - "key": encryptedKey, - "keyDigest": keyDigest, - "value": encryptedValue, - "path": path, - "tags": []string{}, - "comment": "", - } - secrets = append(secrets, secret) - } - } - - // Create the secret - err = network.CreatePhaseSecrets(p.AppToken, envID, secrets, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to create secret: %v", err) - } - return err - } - if p.Debug { - log.Println("Secret created successfully") - } - return nil + decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to decrypt wrapped salt: %v", err) + } + return err + } + + secrets := make([]map[string]interface{}, 0) + for _, pair := range opts.KeyValuePairs { + for key, value := range pair { + encryptedKey, err := crypto.EncryptAsymmetric(key, publicKey) + if err != nil { + if p.Debug { + log.Printf("Failed to encrypt key: %v\n", err) + } + continue + } + + encryptedValue, err := crypto.EncryptAsymmetric(value, publicKey) + if err != nil { + if p.Debug { + log.Printf("Failed to encrypt value: %v\n", err) + } + continue + } + + keyDigest, err := crypto.Blake2bDigest(key, decryptedSalt) + if err != nil { + if p.Debug { + log.Printf("Failed to generate key digest: %v\n", err) + } + continue + } + + // Determine the path for the secret, default to "/" if not specified + path, ok := opts.SecretPath[key] + if !ok { + path = "/" // Default path if not provided + } + + secret := map[string]interface{}{ + "key": encryptedKey, + "keyDigest": keyDigest, + "value": encryptedValue, + "path": path, + "tags": []string{}, + "comment": "", + } + secrets = append(secrets, secret) + } + } + + // Create the secret + err = network.CreatePhaseSecrets(p.AppToken, envID, secrets, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to create secret: %v", err) + } + return err + } + if p.Debug { + log.Println("Secret created successfully") + } + return nil } func (p *Phase) Update(opts SecretUpdateOptions) error { - // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to fetch user data: %v", err) - } - return err - } - defer resp.Body.Close() - - var userData misc.AppKeyResponse - if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { - if p.Debug { - log.Printf("Failed to decode user data: %v", err) - } - return err - } + // Fetch user data + resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to fetch user data: %v", err) + } + return err + } + defer resp.Body.Close() + + var userData misc.AppKeyResponse + if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { + if p.Debug { + log.Printf("Failed to decode user data: %v", err) + } + return err + } envKey, err := misc.FindEnvironmentKey(userData, misc.FindEnvironmentKeyOptions{ EnvName: opts.EnvName, @@ -549,104 +549,104 @@ func (p *Phase) Update(opts SecretUpdateOptions) error { return err } - decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to decrypt wrapped salt: %v", err) - } - return err - } - - // Generate key digest - keyDigest, err := crypto.Blake2bDigest(opts.Key, decryptedSalt) - if err != nil { - if p.Debug { - log.Printf("Failed to generate key digest: %v", err) - } - return err - } - - // Fetch a single secret based on keyDigest - secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) - if err != nil { - if p.Debug { - log.Printf("Failed to fetch secret: %v", err) - } - return err - } - - publicKeyHex := envKey.IdentityKey - - // Encrypt the key and value with the environment's public key - encryptedKey, err := crypto.EncryptAsymmetric(opts.Key, publicKeyHex) - if err != nil { - if p.Debug { - log.Printf("Failed to encrypt key: %v", err) - } - return err - } - - encryptedValue, err := crypto.EncryptAsymmetric(opts.Value, publicKeyHex) - if err != nil { - if p.Debug { - log.Printf("Failed to encrypt value: %v", err) - } - return err - } - - secretID, ok := secret["id"].(string) - if !ok { - return fmt.Errorf("secret ID is not a string") - } - - // Default path to "/" if not provided - if opts.SecretPath == "" { - opts.SecretPath = "/" - } - - secretUpdatePayload := map[string]interface{}{ - "id": secretID, - "key": encryptedKey, - "keyDigest": keyDigest, - "value": encryptedValue, - "path": opts.SecretPath, - "tags": []string{}, - "comment": "", - } - - // Perform the update - err = network.UpdatePhaseSecrets(p.AppToken, envKey.Environment.ID, []map[string]interface{}{secretUpdatePayload}, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to update secret: %v", err) - } - return err - } - if p.Debug { - log.Println("Secret updated successfully") - } - return nil + decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to decrypt wrapped salt: %v", err) + } + return err + } + + // Generate key digest + keyDigest, err := crypto.Blake2bDigest(opts.Key, decryptedSalt) + if err != nil { + if p.Debug { + log.Printf("Failed to generate key digest: %v", err) + } + return err + } + + // Fetch a single secret based on keyDigest + secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) + if err != nil { + if p.Debug { + log.Printf("Failed to fetch secret: %v", err) + } + return err + } + + publicKeyHex := envKey.IdentityKey + + // Encrypt the key and value with the environment's public key + encryptedKey, err := crypto.EncryptAsymmetric(opts.Key, publicKeyHex) + if err != nil { + if p.Debug { + log.Printf("Failed to encrypt key: %v", err) + } + return err + } + + encryptedValue, err := crypto.EncryptAsymmetric(opts.Value, publicKeyHex) + if err != nil { + if p.Debug { + log.Printf("Failed to encrypt value: %v", err) + } + return err + } + + secretID, ok := secret["id"].(string) + if !ok { + return fmt.Errorf("secret ID is not a string") + } + + // Default path to "/" if not provided + if opts.SecretPath == "" { + opts.SecretPath = "/" + } + + secretUpdatePayload := map[string]interface{}{ + "id": secretID, + "key": encryptedKey, + "keyDigest": keyDigest, + "value": encryptedValue, + "path": opts.SecretPath, + "tags": []string{}, + "comment": "", + } + + // Perform the update + err = network.UpdatePhaseSecrets(p.AppToken, envKey.Environment.ID, []map[string]interface{}{secretUpdatePayload}, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to update secret: %v", err) + } + return err + } + if p.Debug { + log.Println("Secret updated successfully") + } + return nil } // DeleteSecret deletes a secret in Phase KMS based on a key and environment. func (p *Phase) Delete(opts DeleteSecretOptions) error { - // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to fetch user data: %v", err) - } - return err - } - defer resp.Body.Close() - - var userData misc.AppKeyResponse - if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { - if p.Debug { - log.Printf("Failed to decode user data: %v", err) - } - return err - } + // Fetch user data + resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to fetch user data: %v", err) + } + return err + } + defer resp.Body.Close() + + var userData misc.AppKeyResponse + if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil { + if p.Debug { + log.Printf("Failed to decode user data: %v", err) + } + return err + } envKey, err := misc.FindEnvironmentKey(userData, misc.FindEnvironmentKeyOptions{ EnvName: opts.EnvName, @@ -660,48 +660,48 @@ func (p *Phase) Delete(opts DeleteSecretOptions) error { return err } - decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to decrypt wrapped salt: %v", err) - } - return err - } - - // Generate key digest - keyDigest, err := crypto.Blake2bDigest(opts.KeyToDelete, decryptedSalt) - if err != nil { - if p.Debug { - log.Printf("Failed to generate key digest: %v", err) - } - return err - } - - // Fetch the specific secret by its key digest and path - secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) - if err != nil { - if p.Debug { - log.Printf("Failed to fetch secret: %v", err) - } - return err - } - - secretID, ok := secret["id"].(string) - if !ok { - return fmt.Errorf("secret ID is not a string for key: %v", opts.KeyToDelete) - } - - // Perform the delete operation for the found secret ID - err = network.DeletePhaseSecrets(p.AppToken, envKey.Environment.ID, []string{secretID}, p.Host) - if err != nil { - if p.Debug { - log.Printf("Failed to delete secret: %v", err) - } - return err - } - - if p.Debug { - log.Println("Secret deleted successfully") - } - return nil + decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to decrypt wrapped salt: %v", err) + } + return err + } + + // Generate key digest + keyDigest, err := crypto.Blake2bDigest(opts.KeyToDelete, decryptedSalt) + if err != nil { + if p.Debug { + log.Printf("Failed to generate key digest: %v", err) + } + return err + } + + // Fetch the specific secret by its key digest and path + secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) + if err != nil { + if p.Debug { + log.Printf("Failed to fetch secret: %v", err) + } + return err + } + + secretID, ok := secret["id"].(string) + if !ok { + return fmt.Errorf("secret ID is not a string for key: %v", opts.KeyToDelete) + } + + // Perform the delete operation for the found secret ID + err = network.DeletePhaseSecrets(p.AppToken, envKey.Environment.ID, []string{secretID}, p.Host) + if err != nil { + if p.Debug { + log.Printf("Failed to delete secret: %v", err) + } + return err + } + + if p.Debug { + log.Println("Secret deleted successfully") + } + return nil } From d2ffae8488f29b1c2cb4f26f0e0ccfebcaf8a919 Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 13:51:51 +0530 Subject: [PATCH 2/9] feat: add support for tokenType context being passed to utils in network.go --- phase/network/network.go | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/phase/network/network.go b/phase/network/network.go index aaf04fc..4672c56 100644 --- a/phase/network/network.go +++ b/phase/network/network.go @@ -16,10 +16,9 @@ import ( "github.com/phasehq/golang-sdk/phase/misc" ) -func ConstructHTTPHeaders(appToken string) http.Header { +func ConstructHTTPHeaders(tokenType string, appToken string) http.Header { headers := http.Header{} - // Adjusting to use "Bearer Service" as part of the token value - headers.Set("Authorization", fmt.Sprintf("Bearer Service %s", appToken)) + headers.Set("Authorization", fmt.Sprintf("Bearer %s %s", tokenType, appToken)) headers.Set("User-Agent", GetUserAgent()) return headers } @@ -93,7 +92,7 @@ func handleHTTPResponse(resp *http.Response) error { } } -func FetchPhaseUser(appToken, host string) (*http.Response, error) { +func FetchPhaseUser(tokenType, appToken, host string) (*http.Response, error) { client := createHTTPClient() url := fmt.Sprintf("%s/service/secrets/tokens/", host) req, err := http.NewRequest("GET", url, nil) @@ -101,7 +100,7 @@ func FetchPhaseUser(appToken, host string) (*http.Response, error) { return nil, err } - req.Header = ConstructHTTPHeaders(appToken) + req.Header = ConstructHTTPHeaders(tokenType, appToken) resp, err := client.Do(req) if err != nil { return nil, err @@ -139,7 +138,7 @@ type AppKeyResponse struct { } `json:"apps"` } -func FetchAppKey(appToken, host string) (string, error) { +func FetchAppKey(tokenType, appToken, host string) (string, error) { client := createHTTPClient() url := fmt.Sprintf("%s/service/secrets/tokens/", host) req, err := http.NewRequest("GET", url, nil) @@ -147,7 +146,7 @@ func FetchAppKey(appToken, host string) (string, error) { return "", err } - req.Header = ConstructHTTPHeaders(appToken) + req.Header = ConstructHTTPHeaders(tokenType, appToken) resp, err := client.Do(req) if err != nil { return "", err @@ -166,7 +165,7 @@ func FetchAppKey(appToken, host string) (string, error) { return jsonResp.WrappedKeyShare, nil } -func FetchPhaseSecrets(appToken, environmentID, host, path string) ([]map[string]interface{}, error) { +func FetchPhaseSecrets(tokenType, appToken, environmentID, host, path string) ([]map[string]interface{}, error) { client := createHTTPClient() url := fmt.Sprintf("%s/service/secrets/", host) @@ -175,7 +174,7 @@ func FetchPhaseSecrets(appToken, environmentID, host, path string) ([]map[string return nil, err } - req.Header = ConstructHTTPHeaders(appToken) + req.Header = ConstructHTTPHeaders(tokenType, appToken) req.Header.Set("Environment", environmentID) if path != "" { req.Header.Set("Path", path) @@ -199,7 +198,7 @@ func FetchPhaseSecrets(appToken, environmentID, host, path string) ([]map[string return secrets, nil } -func FetchPhaseSecret(appToken, environmentID, host, keyDigest, path string) (map[string]interface{}, error) { +func FetchPhaseSecret(tokenType, appToken, environmentID, host, keyDigest, path string) (map[string]interface{}, error) { client := createHTTPClient() url := fmt.Sprintf("%s/service/secrets/", host) @@ -208,7 +207,7 @@ func FetchPhaseSecret(appToken, environmentID, host, keyDigest, path string) (ma return nil, err } - req.Header = ConstructHTTPHeaders(appToken) + req.Header = ConstructHTTPHeaders(tokenType, appToken) req.Header.Set("Environment", environmentID) req.Header.Set("KeyDigest", keyDigest) if path != "" { @@ -237,7 +236,7 @@ func FetchPhaseSecret(appToken, environmentID, host, keyDigest, path string) (ma return nil, fmt.Errorf("no secrets found in the response") } -func CreatePhaseSecrets(appToken, environmentID string, secrets []map[string]interface{}, host string) error { +func CreatePhaseSecrets(tokenType, appToken, environmentID string, secrets []map[string]interface{}, host string) error { client := createHTTPClient() url := fmt.Sprintf("%s/service/secrets/", host) data, err := json.Marshal(map[string][]map[string]interface{}{"secrets": secrets}) @@ -250,7 +249,7 @@ func CreatePhaseSecrets(appToken, environmentID string, secrets []map[string]int return err } - req.Header = ConstructHTTPHeaders(appToken) + req.Header = ConstructHTTPHeaders(tokenType, appToken) req.Header.Set("Environment", environmentID) resp, err := client.Do(req) @@ -262,7 +261,7 @@ func CreatePhaseSecrets(appToken, environmentID string, secrets []map[string]int return handleHTTPResponse(resp) } -func UpdatePhaseSecrets(appToken, environmentID string, secrets []map[string]interface{}, host string) error { +func UpdatePhaseSecrets(tokenType, appToken, environmentID string, secrets []map[string]interface{}, host string) error { client := createHTTPClient() url := fmt.Sprintf("%s/service/secrets/", host) data, err := json.Marshal(map[string][]map[string]interface{}{"secrets": secrets}) @@ -275,7 +274,7 @@ func UpdatePhaseSecrets(appToken, environmentID string, secrets []map[string]int return err } - req.Header = ConstructHTTPHeaders(appToken) + req.Header = ConstructHTTPHeaders(tokenType, appToken) req.Header.Set("Environment", environmentID) resp, err := client.Do(req) @@ -287,7 +286,7 @@ func UpdatePhaseSecrets(appToken, environmentID string, secrets []map[string]int return handleHTTPResponse(resp) } -func DeletePhaseSecrets(appToken, environmentID string, secretIDs []string, host string) error { +func DeletePhaseSecrets(tokenType, appToken, environmentID string, secretIDs []string, host string) error { client := createHTTPClient() url := fmt.Sprintf("%s/service/secrets/", host) data, err := json.Marshal(map[string][]string{"secrets": secretIDs}) @@ -300,7 +299,7 @@ func DeletePhaseSecrets(appToken, environmentID string, secretIDs []string, host return err } - req.Header = ConstructHTTPHeaders(appToken) + req.Header = ConstructHTTPHeaders(tokenType, appToken) req.Header.Set("Environment", environmentID) resp, err := client.Do(req) From 7258b84ade6ac091e9ecd6c6bc0e17725e1be07e Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 13:52:22 +0530 Subject: [PATCH 3/9] feat: pass TokenType context to decrypt wrapped keyshare --- phase/crypto/crypto.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phase/crypto/crypto.go b/phase/crypto/crypto.go index 77a6c30..5f85782 100644 --- a/phase/crypto/crypto.go +++ b/phase/crypto/crypto.go @@ -235,9 +235,9 @@ func DecryptSecret(secret map[string]interface{}, privateKeyHex, publicKeyHex st } // Decrypt decrypts the provided ciphertext using the Phase encryption mechanism. -func DecryptWrappedKeyShare(Keyshare1 string, Keyshare0 string, AppToken string, Keyshare1UnwrapKey string, PssUserPublicKey string, Host string) (string, error) { +func DecryptWrappedKeyShare(Keyshare1 string, Keyshare0 string, TokenType string, AppToken string, Keyshare1UnwrapKey string, PssUserPublicKey string, Host string) (string, error) { // Fetch the wrapped key share using the app token and host - wrappedKeyShare, err := network.FetchAppKey(AppToken, Host) + wrappedKeyShare, err := network.FetchAppKey(TokenType, AppToken, Host) if err != nil { log.Fatalf("Failed to fetch wrapped key share: %v", err) return "", err From ecd05391fdc149f8ee99d7701388c129775e87a2 Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 13:52:51 +0530 Subject: [PATCH 4/9] feat: added TokenType in Phase struct --- phase/phase.go | 1 + 1 file changed, 1 insertion(+) diff --git a/phase/phase.go b/phase/phase.go index 8722bf1..33b4379 100644 --- a/phase/phase.go +++ b/phase/phase.go @@ -21,6 +21,7 @@ type Phase struct { Keyshare1UnwrapKey string Host string Debug bool + TokenType string } type GetSecretOptions struct { From c89d7ba0a0ee194cebb540f0666003b2ac7d9261 Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 13:54:12 +0530 Subject: [PATCH 5/9] feat: pass TokenType context to utils --- phase/phase.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/phase/phase.go b/phase/phase.go index 33b4379..ba068c8 100644 --- a/phase/phase.go +++ b/phase/phase.go @@ -174,7 +174,7 @@ func (p *Phase) resolveSecretValue(value string, currentEnvName string) (string, // Get fetches and decrypts a secret, resolving any secret references within its value. func (p *Phase) Get(opts GetSecretOptions) (*map[string]interface{}, error) { // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + resp, err := network.FetchPhaseUser(p.TokenType, p.AppToken, p.Host) if err != nil { if p.Debug { log.Printf("Failed to fetch user data: %v", err) @@ -203,14 +203,14 @@ func (p *Phase) Get(opts GetSecretOptions) (*map[string]interface{}, error) { return nil, err } - decryptedSeed, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSeed, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + decryptedSeed, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSeed, p.Keyshare0, p.TokenType, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) if err != nil { if p.Debug { log.Printf("Failed to decrypt wrapped seed: %v", err) } return nil, err } - decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.TokenType, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) if err != nil { if p.Debug { log.Printf("Failed to decrypt wrapped salt: %v", err) @@ -235,7 +235,7 @@ func (p *Phase) Get(opts GetSecretOptions) (*map[string]interface{}, error) { } // Fetch a single secret based on keyDigest and optional path - secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) + secret, err := network.FetchPhaseSecret(p.TokenType, p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) if err != nil { if p.Debug { log.Printf("Failed to fetch secret: %v", err) @@ -295,7 +295,7 @@ func (p *Phase) Get(opts GetSecretOptions) (*map[string]interface{}, error) { func (p *Phase) GetAll(opts GetAllSecretsOptions) ([]map[string]interface{}, error) { // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + resp, err := network.FetchPhaseUser(p.TokenType, p.AppToken, p.Host) if err != nil { if p.Debug { log.Printf("Failed to fetch user data: %v", err) @@ -325,7 +325,7 @@ func (p *Phase) GetAll(opts GetAllSecretsOptions) ([]map[string]interface{}, err } // Decrypt the wrapped seed - decryptedSeed, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSeed, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + decryptedSeed, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSeed, p.Keyshare0, p.TokenType, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) if err != nil { if p.Debug { log.Printf("Failed to decrypt wrapped seed: %v", err) @@ -343,7 +343,7 @@ func (p *Phase) GetAll(opts GetAllSecretsOptions) ([]map[string]interface{}, err } // Fetch secrets with optional path filtering - secrets, err := network.FetchPhaseSecrets(p.AppToken, envKey.Environment.ID, p.Host, opts.SecretPath) + secrets, err := network.FetchPhaseSecrets(p.TokenType, p.AppToken, envKey.Environment.ID, p.Host, opts.SecretPath) if err != nil { if p.Debug { log.Printf("Failed to fetch secrets: %v", err) @@ -411,7 +411,7 @@ func (p *Phase) GetAll(opts GetAllSecretsOptions) ([]map[string]interface{}, err // CreateSecrets creates new secrets in the Phase KMS for the specified environment and application. func (p *Phase) Create(opts CreateSecretsOptions) error { // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + resp, err := network.FetchPhaseUser(p.TokenType, p.AppToken, p.Host) if err != nil { if p.Debug { log.Printf("Failed to fetch user data: %v", err) @@ -452,7 +452,7 @@ func (p *Phase) Create(opts CreateSecretsOptions) error { return err } - decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.TokenType, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) if err != nil { if p.Debug { log.Printf("Failed to decrypt wrapped salt: %v", err) @@ -506,7 +506,7 @@ func (p *Phase) Create(opts CreateSecretsOptions) error { } // Create the secret - err = network.CreatePhaseSecrets(p.AppToken, envID, secrets, p.Host) + err = network.CreatePhaseSecrets(p.TokenType, p.AppToken, envID, secrets, p.Host) if err != nil { if p.Debug { log.Printf("Failed to create secret: %v", err) @@ -521,7 +521,7 @@ func (p *Phase) Create(opts CreateSecretsOptions) error { func (p *Phase) Update(opts SecretUpdateOptions) error { // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + resp, err := network.FetchPhaseUser(p.TokenType, p.AppToken, p.Host) if err != nil { if p.Debug { log.Printf("Failed to fetch user data: %v", err) @@ -550,7 +550,7 @@ func (p *Phase) Update(opts SecretUpdateOptions) error { return err } - decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.TokenType, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) if err != nil { if p.Debug { log.Printf("Failed to decrypt wrapped salt: %v", err) @@ -568,7 +568,7 @@ func (p *Phase) Update(opts SecretUpdateOptions) error { } // Fetch a single secret based on keyDigest - secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) + secret, err := network.FetchPhaseSecret(p.TokenType, p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) if err != nil { if p.Debug { log.Printf("Failed to fetch secret: %v", err) @@ -616,7 +616,7 @@ func (p *Phase) Update(opts SecretUpdateOptions) error { } // Perform the update - err = network.UpdatePhaseSecrets(p.AppToken, envKey.Environment.ID, []map[string]interface{}{secretUpdatePayload}, p.Host) + err = network.UpdatePhaseSecrets(p.TokenType, p.AppToken, envKey.Environment.ID, []map[string]interface{}{secretUpdatePayload}, p.Host) if err != nil { if p.Debug { log.Printf("Failed to update secret: %v", err) @@ -632,7 +632,7 @@ func (p *Phase) Update(opts SecretUpdateOptions) error { // DeleteSecret deletes a secret in Phase KMS based on a key and environment. func (p *Phase) Delete(opts DeleteSecretOptions) error { // Fetch user data - resp, err := network.FetchPhaseUser(p.AppToken, p.Host) + resp, err := network.FetchPhaseUser(p.TokenType, p.AppToken, p.Host) if err != nil { if p.Debug { log.Printf("Failed to fetch user data: %v", err) @@ -661,7 +661,7 @@ func (p *Phase) Delete(opts DeleteSecretOptions) error { return err } - decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) + decryptedSalt, err := crypto.DecryptWrappedKeyShare(envKey.WrappedSalt, p.Keyshare0, p.TokenType, p.AppToken, p.Keyshare1UnwrapKey, p.PssUserPublicKey, p.Host) if err != nil { if p.Debug { log.Printf("Failed to decrypt wrapped salt: %v", err) @@ -679,7 +679,7 @@ func (p *Phase) Delete(opts DeleteSecretOptions) error { } // Fetch the specific secret by its key digest and path - secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.Host, keyDigest, opts.SecretPath) + secret, err := network.FetchPhaseSecret(p.AppToken, envKey.Environment.ID, p.TokenType, p.Host, keyDigest, opts.SecretPath) if err != nil { if p.Debug { log.Printf("Failed to fetch secret: %v", err) @@ -693,7 +693,7 @@ func (p *Phase) Delete(opts DeleteSecretOptions) error { } // Perform the delete operation for the found secret ID - err = network.DeletePhaseSecrets(p.AppToken, envKey.Environment.ID, []string{secretID}, p.Host) + err = network.DeletePhaseSecrets(p.TokenType, p.AppToken, envKey.Environment.ID, []string{secretID}, p.Host) if err != nil { if p.Debug { log.Printf("Failed to delete secret: %v", err) From 248f9c4bc1861a44d398963b2f1a5f3ad81d47b8 Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 14:08:38 +0530 Subject: [PATCH 6/9] feat: added service account support in init --- phase/phase.go | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/phase/phase.go b/phase/phase.go index ba068c8..6aee61e 100644 --- a/phase/phase.go +++ b/phase/phase.go @@ -68,27 +68,44 @@ type DeleteSecretOptions struct { // Init initializes a new instance of Phase with the provided service token, host, and debug flag. func Init(serviceToken, host string, debug bool) *Phase { - // Validate the service token against the pattern. - matches := misc.PssServicePattern.FindStringSubmatch(serviceToken) - if matches == nil || len(matches) != 6 { + + // Validate the service token against the pattern + serviceMatches := misc.PssServicePattern.FindStringSubmatch(serviceToken) + userMatches := misc.PssUserPattern.FindStringSubmatch(serviceToken) + + // Check if it's a valid service token + if serviceMatches == nil || len(serviceMatches) != 6 { + if userMatches != nil { + log.Fatalf("Error: User token provided. Expected service token.") + } log.Fatalf("Error: Invalid Phase Service Token.") } - // Use default host if none is specified. + // Use Phase Cloud if no host is provided if host == "" { host = misc.PhaseCloudAPIHost } - // Create a new Phase instance with parsed service token components and debug flag. + // Get version from regex group 1 (the capture group for 'v(\d+)') + version := serviceMatches[1] // This will be "2" for v2 tokens + + // Determine token type based on version + tokenType := "Service" + if version == "2" { + tokenType = "ServiceAccount" + } + + // Create a new Phase instance with parsed service token components and debug flag return &Phase{ Prefix: "pss_service", - PesVersion: matches[1], - AppToken: matches[2], - PssUserPublicKey: matches[3], - Keyshare0: matches[4], - Keyshare1UnwrapKey: matches[5], + PesVersion: "v" + version, + AppToken: serviceMatches[2], + PssUserPublicKey: serviceMatches[3], + Keyshare0: serviceMatches[4], + Keyshare1UnwrapKey: serviceMatches[5], Host: host, Debug: debug, + TokenType: tokenType, } } From 21fe179a040b91d9d168974a151eb5cf48847839 Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 14:09:30 +0530 Subject: [PATCH 7/9] fix: verify ssl --- phase/misc/const.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phase/misc/const.go b/phase/misc/const.go index 56f3e15..f10e10f 100644 --- a/phase/misc/const.go +++ b/phase/misc/const.go @@ -11,7 +11,7 @@ const ( ) var ( - VerifySSL = false + VerifySSL = true PhaseDebug = false ) From 7494ffdd3b7e07afec6730d5f457f04eac6ed552 Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 14:09:48 +0530 Subject: [PATCH 8/9] chore: bumped version --- phase/misc/const.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phase/misc/const.go b/phase/misc/const.go index f10e10f..9f6e23d 100644 --- a/phase/misc/const.go +++ b/phase/misc/const.go @@ -5,7 +5,7 @@ import ( ) const ( - Version = "1.0.1" + Version = "1.0.2" PhVersion = "v1" PhaseCloudAPIHost = "https://console.phase.dev" ) From 5ce82c5d51c0a6d1ada5297550b28e206b68c73d Mon Sep 17 00:00:00 2001 From: Nimish Date: Tue, 29 Oct 2024 14:39:07 +0530 Subject: [PATCH 9/9] fix: secret referencing bug --- phase/misc/misc.go | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/phase/misc/misc.go b/phase/misc/misc.go index 0ed88d7..dada4ec 100644 --- a/phase/misc/misc.go +++ b/phase/misc/misc.go @@ -19,22 +19,46 @@ func PhaseGetContext(userData AppKeyResponse, opts GetContextOptions) (string, s return "", "", "", fmt.Errorf("matching context not found") } -// FindEnvironmentKey searches for an environment key with case-insensitive and partial matching. +// FindEnvironmentKey searches for an environment key with case-insensitive matching. func FindEnvironmentKey(userData AppKeyResponse, opts FindEnvironmentKeyOptions) (*EnvironmentKey, error) { - lcEnvName := strings.ToLower(opts.EnvName) - lcAppName := strings.ToLower(opts.AppName) + lcEnvName := strings.ToLower(strings.TrimSpace(opts.EnvName)) - for _, app := range userData.Apps { - if (opts.AppID != "" && app.ID == opts.AppID) || - (opts.AppName != "" && (opts.AppName == "" || strings.Contains(strings.ToLower(app.Name), lcAppName))) { + // If no app specified, try all apps + if opts.AppName == "" && opts.AppID == "" { + for _, app := range userData.Apps { for _, envKey := range app.EnvironmentKeys { - if strings.Contains(strings.ToLower(envKey.Environment.Name), lcEnvName) { + if strings.EqualFold(strings.TrimSpace(envKey.Environment.Name), lcEnvName) { return &envKey, nil } } } + } else { + // Check specific app + for _, app := range userData.Apps { + if (opts.AppID != "" && app.ID == opts.AppID) || + (opts.AppName != "" && strings.EqualFold(app.Name, opts.AppName)) { + + for _, envKey := range app.EnvironmentKeys { + if strings.EqualFold(strings.TrimSpace(envKey.Environment.Name), lcEnvName) { + return &envKey, nil + } + } + } + } } - return nil, fmt.Errorf("environment key not found for app '%s' (ID: %s) and environment '%s'", opts.AppName, opts.AppID, opts.EnvName) + + // If exact match not found, try partial matches + for _, app := range userData.Apps { + for _, envKey := range app.EnvironmentKeys { + envName := strings.ToLower(strings.TrimSpace(envKey.Environment.Name)) + if strings.Contains(envName, lcEnvName) { + return &envKey, nil + } + } + } + + return nil, fmt.Errorf("environment key not found for app '%s' (ID: %s) and environment '%s'", + opts.AppName, opts.AppID, opts.EnvName) } // normalizeTag replaces underscores with spaces and converts the string to lower case. @@ -42,7 +66,7 @@ func normalizeTag(tag string) string { return strings.ToLower(strings.Replace(tag, "_", " ", -1)) } -// tagMatches checks if the user-provided tag partially matches any of the secret tags. +// TagMatches checks if the user-provided tag partially matches any of the secret tags. func TagMatches(secretTags []string, userTag string) bool { normalizedUserTag := normalizeTag(userTag) for _, tag := range secretTags {