44 "encoding/json"
55 "fmt"
66 "log"
7+ "strings"
78
89 "github.com/phasehq/golang-sdk/phase/crypto"
910 "github.com/phasehq/golang-sdk/phase/misc"
@@ -85,6 +86,86 @@ func Init(serviceToken, host string, debug bool) *Phase {
8586 }
8687}
8788
89+ func (p * Phase ) resolveSecretReference (ref , currentEnvName string ) (string , error ) {
90+ var envName , path , keyName string
91+
92+ // Check if the reference starts with an environment name followed by a dot
93+ if strings .Contains (ref , "." ) {
94+ // Split on the first dot to differentiate environment from path/key
95+ parts := strings .SplitN (ref , "." , 2 )
96+ envName = parts [0 ]
97+
98+ // Further split the second part to separate the path and the key
99+ // The last segment after the last "/" is the key, the rest is the path
100+ lastSlashIndex := strings .LastIndex (parts [1 ], "/" )
101+ if lastSlashIndex != - 1 { // Path is specified
102+ path = parts [1 ][:lastSlashIndex ] // Include the slash in the path
103+ keyName = parts [1 ][lastSlashIndex + 1 :]
104+ } else { // No path specified, use root
105+ path = "/"
106+ keyName = parts [1 ]
107+ }
108+ } else { // Local reference without an environment prefix
109+ envName = currentEnvName
110+ lastSlashIndex := strings .LastIndex (ref , "/" )
111+ if lastSlashIndex != - 1 { // Path is specified
112+ path = ref [:lastSlashIndex ] // Include the slash in the path
113+ keyName = ref [lastSlashIndex + 1 :]
114+ } else { // No path specified, use root
115+ path = "/"
116+ keyName = ref
117+ }
118+ }
119+
120+ // Validate the extracted parts
121+ if keyName == "" {
122+ return "" , fmt .Errorf ("invalid secret reference format: %s" , ref )
123+ }
124+
125+ // Fetch and decrypt the referenced secret
126+ opts := GetSecretOptions {
127+ EnvName : envName ,
128+ AppName : "" , // AppName is available globally
129+ KeyToFind : keyName ,
130+ SecretPath : path ,
131+ }
132+ resolvedSecret , err := p .Get (opts )
133+ if err != nil {
134+ return "" , fmt .Errorf ("failed to resolve secret reference %s: %v" , ref , err )
135+ }
136+
137+ // Return the decrypted value of the referenced secret
138+ decryptedValue , ok := (* resolvedSecret )["value" ].(string )
139+ if ! ok {
140+ return "" , fmt .Errorf ("decrypted value of the secret reference %s is not a string" , ref )
141+ }
142+
143+ return decryptedValue , nil
144+ }
145+
146+ // resolveSecretValue resolves all secret references in a given value string.
147+ func (p * Phase ) resolveSecretValue (value string , currentEnvName string ) (string , error ) {
148+ refs := misc .SecretRefRegex .FindAllString (value , - 1 )
149+ resolvedValue := value
150+
151+ for _ , ref := range refs {
152+ // Extract just the reference part without the surrounding ${}
153+ refMatch := misc .SecretRefRegex .FindStringSubmatch (ref )
154+ if len (refMatch ) > 1 {
155+ // Pass the current environment name if needed for resolution
156+ resolvedSecretValue , err := p .resolveSecretReference (refMatch [1 ], currentEnvName )
157+ if err != nil {
158+ return "" , err
159+ }
160+ // Directly use the string value returned by resolveSecretReference
161+ resolvedValue = strings .Replace (resolvedValue , ref , resolvedSecretValue , - 1 )
162+ }
163+ }
164+
165+ return resolvedValue , nil
166+ }
167+
168+ // Get fetches and decrypts a secret, resolving any secret references within its value.
88169func (p * Phase ) Get (opts GetSecretOptions ) (* map [string ]interface {}, error ) {
89170 // Fetch user data
90171 resp , err := network .FetchPhaseUser (p .AppToken , p .Host )
@@ -160,6 +241,15 @@ func (p *Phase) Get(opts GetSecretOptions) (*map[string]interface{}, error) {
160241 return nil , err
161242 }
162243
244+ // Resolve any secret references within the decryptedValue before creating the result map
245+ resolvedValue , err := p .resolveSecretValue (decryptedValue , opts .EnvName )
246+ if err != nil {
247+ if p .Debug {
248+ log .Printf ("Failed to resolve secret value: %v" , err )
249+ }
250+ return nil , err
251+ }
252+
163253 // Verify tag match if a tag is provided
164254 var stringTags []string
165255 if opts .Tag != "" {
@@ -180,7 +270,7 @@ func (p *Phase) Get(opts GetSecretOptions) (*map[string]interface{}, error) {
180270
181271 result := & map [string ]interface {}{
182272 "key" : decryptedKey ,
183- "value" : decryptedValue ,
273+ "value" : resolvedValue , // Use resolvedValue here
184274 "comment" : decryptedComment ,
185275 "tags" : stringTags ,
186276 "path" : secretPath ,
@@ -259,6 +349,15 @@ func (p *Phase) GetAll(opts GetAllSecretsOptions) ([]map[string]interface{}, err
259349 continue
260350 }
261351
352+ // Resolve any secret references within the decryptedValue
353+ resolvedValue , err := p .resolveSecretValue (decryptedValue , opts .EnvName )
354+ if err != nil {
355+ if p .Debug {
356+ log .Printf ("Failed to resolve secret value: %v\n " , err )
357+ }
358+ continue
359+ }
360+
262361 // Prepare tags for inclusion in result
263362 var stringTags []string
264363 if secretTags , ok := secret ["tags" ].([]interface {}); ok {
@@ -280,7 +379,7 @@ func (p *Phase) GetAll(opts GetAllSecretsOptions) ([]map[string]interface{}, err
280379 // Append decrypted secret with path to result list
281380 result := map [string ]interface {}{
282381 "key" : decryptedKey ,
283- "value" : decryptedValue ,
382+ "value" : resolvedValue , // Use resolvedValue here
284383 "comment" : decryptedComment ,
285384 "tags" : stringTags ,
286385 "path" : path ,
0 commit comments