Merge pull request #2575 from akhilmhdh/feat/secret-path-cli-template

feat: added secret path to template and optional more arguments as js…
This commit is contained in:
Maidul Islam
2024-10-14 17:19:45 -07:00
committed by GitHub
10 changed files with 120 additions and 216 deletions

View File

@@ -415,6 +415,10 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
req.SetQueryParam("recursive", "true")
}
if request.ExpandSecretReferences {
req.SetQueryParam("expandSecretReferences", "true")
}
response, err := req.Get(fmt.Sprintf("%v/v3/secrets/raw", config.INFISICAL_URL))
if err != nil {

View File

@@ -569,12 +569,13 @@ type CreateDynamicSecretLeaseV1Response struct {
}
type GetRawSecretsV3Request struct {
Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"`
SecretPath string `json:"secretPath"`
IncludeImport bool `json:"include_imports"`
Recursive bool `json:"recursive"`
TagSlugs string `json:"tagSlugs,omitempty"`
Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"`
SecretPath string `json:"secretPath"`
IncludeImport bool `json:"include_imports"`
Recursive bool `json:"recursive"`
TagSlugs string `json:"tagSlugs,omitempty"`
ExpandSecretReferences bool `json:"expandSecretReferences,omitempty"`
}
type GetRawSecretsV3Response struct {
@@ -587,6 +588,7 @@ type GetRawSecretsV3Response struct {
SecretKey string `json:"secretKey"`
SecretValue string `json:"secretValue"`
SecretComment string `json:"secretComment"`
SecretPath string `json:"secretPath"`
} `json:"secrets"`
Imports []ImportedRawSecretV3 `json:"imports"`
ETag string
@@ -610,6 +612,7 @@ type GetRawSecretV3ByNameResponse struct {
SecretKey string `json:"secretKey"`
SecretValue string `json:"secretValue"`
SecretComment string `json:"secretComment"`
SecretPath string `json:"secretPath"`
} `json:"secret"`
ETag string
}

View File

@@ -7,6 +7,7 @@ import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"os"
@@ -311,9 +312,34 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
return config, nil
}
func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) {
return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) {
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false, "")
type secretArguments struct {
IsRecursive bool `json:"recursive"`
ShouldExpandSecretReferences *bool `json:"expandSecretReferences,omitempty"`
}
func (s *secretArguments) SetDefaults() {
if s.ShouldExpandSecretReferences == nil {
var bool = true
s.ShouldExpandSecretReferences = &bool
}
}
func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string, ...string) ([]models.SingleEnvironmentVariable, error) {
// ...string is because golang doesn't have optional arguments.
// thus we make it slice and pick it only first element
return func(projectID, envSlug, secretPath string, args ...string) ([]models.SingleEnvironmentVariable, error) {
var parsedArguments secretArguments
// to make it optional
if len(args) > 0 {
err := json.Unmarshal([]byte(args[0]), &parsedArguments)
if err != nil {
return nil, err
}
}
parsedArguments.SetDefaults()
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, parsedArguments.IsRecursive, "", *parsedArguments.ShouldExpandSecretReferences)
if err != nil {
return nil, err
}
@@ -322,9 +348,7 @@ func secretTemplateFunction(accessToken string, existingEtag string, currentEtag
*currentEtag = res.Etag
}
expandedSecrets := util.ExpandSecrets(res.Secrets, models.ExpandSecretsAuthentication{UniversalAuthAccessToken: accessToken}, "")
return expandedSecrets, nil
return res.Secrets, nil
}
}
@@ -456,7 +480,6 @@ func ProcessLiteralTemplate(templateId int, templateString string, data interfac
return &buf, nil
}
type AgentManager struct {
accessToken string
accessTokenTTL time.Duration

View File

@@ -87,11 +87,12 @@ var exportCmd = &cobra.Command{
}
request := models.GetAllSecretsParameters{
Environment: environmentName,
TagSlugs: tagSlugs,
WorkspaceId: projectId,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Environment: environmentName,
TagSlugs: tagSlugs,
WorkspaceId: projectId,
SecretsPath: secretsPath,
IncludeImport: includeImports,
ExpandSecretReferences: shouldExpandSecrets,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
@@ -137,18 +138,6 @@ var exportCmd = &cobra.Command{
}
var output string
if shouldExpandSecrets {
authParams := models.ExpandSecretsAuthentication{}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
authParams.UniversalAuthAccessToken = token.Token
}
secrets = util.ExpandSecrets(secrets, authParams, "")
}
secrets = util.FilterSecretsByTag(secrets, tagSlugs)
secrets = util.SortSecretsByKeys(secrets)

View File

@@ -137,15 +137,16 @@ var runCmd = &cobra.Command{
}
request := models.GetAllSecretsParameters{
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Recursive: recursive,
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Recursive: recursive,
ExpandSecretReferences: shouldExpandSecrets,
}
injectableEnvironment, err := fetchAndFormatSecretsForShell(request, projectConfigDir, secretOverriding, shouldExpandSecrets, token)
injectableEnvironment, err := fetchAndFormatSecretsForShell(request, projectConfigDir, secretOverriding, token)
if err != nil {
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")
}
@@ -153,7 +154,7 @@ var runCmd = &cobra.Command{
log.Debug().Msgf("injecting the following environment variables into shell: %v", injectableEnvironment.Variables)
if watchMode {
executeCommandWithWatchMode(command, args, watchModeInterval, request, projectConfigDir, shouldExpandSecrets, secretOverriding, token)
executeCommandWithWatchMode(command, args, watchModeInterval, request, projectConfigDir, secretOverriding, token)
} else {
if cmd.Flags().Changed("command") {
command := cmd.Flag("command").Value.String()
@@ -306,7 +307,7 @@ func waitForExitCommand(cmd *exec.Cmd) (int, error) {
return waitStatus.ExitStatus(), nil
}
func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInterval int, request models.GetAllSecretsParameters, projectConfigDir string, expandSecrets bool, secretOverriding bool, token *models.TokenDetails) {
func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInterval int, request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) {
var cmd *exec.Cmd
var err error
@@ -420,7 +421,7 @@ func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInt
<-recheckSecretsChannel
watchMutex.Lock()
newEnvironmentVariables, err := fetchAndFormatSecretsForShell(request, projectConfigDir, secretOverriding, expandSecrets, token)
newEnvironmentVariables, err := fetchAndFormatSecretsForShell(request, projectConfigDir, secretOverriding, token)
if err != nil {
log.Error().Err(err).Msg("[HOT RELOAD] Failed to fetch secrets")
continue
@@ -437,7 +438,7 @@ func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInt
}
}
func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, shouldExpandSecrets bool, token *models.TokenDetails) (models.InjectableEnvironmentResult, error) {
func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) (models.InjectableEnvironmentResult, error) {
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
@@ -457,19 +458,6 @@ func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, proje
secrets = util.OverrideSecrets(secrets, util.SECRET_TYPE_SHARED)
}
if shouldExpandSecrets {
authParams := models.ExpandSecretsAuthentication{}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
authParams.UniversalAuthAccessToken = token.Token
}
secrets = util.ExpandSecrets(secrets, authParams, projectConfigDir)
}
secretsByKey := getSecretsByKeys(secrets)
environmentVariables := make(map[string]string)

View File

@@ -79,12 +79,13 @@ var secretsCmd = &cobra.Command{
}
request := models.GetAllSecretsParameters{
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Recursive: recursive,
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Recursive: recursive,
ExpandSecretReferences: shouldExpandSecrets,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
@@ -104,17 +105,6 @@ var secretsCmd = &cobra.Command{
secrets = util.OverrideSecrets(secrets, util.SECRET_TYPE_SHARED)
}
if shouldExpandSecrets {
authParams := models.ExpandSecretsAuthentication{}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
authParams.UniversalAuthAccessToken = token.Token
}
secrets = util.ExpandSecrets(secrets, authParams, "")
}
// Sort the secrets by key so we can create a consistent output
secrets = util.SortSecretsByKeys(secrets)
@@ -382,12 +372,13 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
}
request := models.GetAllSecretsParameters{
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Recursive: recursive,
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Recursive: recursive,
ExpandSecretReferences: shouldExpand,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
@@ -407,17 +398,6 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
secrets = util.OverrideSecrets(secrets, util.SECRET_TYPE_SHARED)
}
if shouldExpand {
authParams := models.ExpandSecretsAuthentication{}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
authParams.UniversalAuthAccessToken = token.Token
}
secrets = util.ExpandSecrets(secrets, authParams, "")
}
requestedSecrets := []models.SingleEnvironmentVariable{}
secretsMap := getSecretsByKeys(secrets)

View File

@@ -30,6 +30,7 @@ type SingleEnvironmentVariable struct {
Value string `json:"value"`
Type string `json:"type"`
ID string `json:"_id"`
SecretPath string `json:"secretPath"`
Tags []struct {
ID string `json:"_id"`
Name string `json:"name"`
@@ -103,6 +104,7 @@ type GetAllSecretsParameters struct {
SecretsPath string
IncludeImport bool
Recursive bool
ExpandSecretReferences bool
}
type InjectableEnvironmentResult struct {

View File

@@ -8,8 +8,6 @@ import (
"errors"
"fmt"
"os"
"path"
"regexp"
"strings"
"unicode"
@@ -21,7 +19,7 @@ import (
"github.com/zalando/go-keyring"
)
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string) ([]models.SingleEnvironmentVariable, error) {
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string, expandSecretReferences bool) ([]models.SingleEnvironmentVariable, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
@@ -49,12 +47,13 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
}
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, api.GetRawSecretsV3Request{
WorkspaceId: serviceTokenDetails.Workspace,
Environment: environment,
SecretPath: secretPath,
IncludeImport: includeImports,
Recursive: recursive,
TagSlugs: tagSlugs,
WorkspaceId: serviceTokenDetails.Workspace,
Environment: environment,
SecretPath: secretPath,
IncludeImport: includeImports,
Recursive: recursive,
TagSlugs: tagSlugs,
ExpandSecretReferences: expandSecretReferences,
})
if err != nil {
@@ -78,17 +77,18 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
}
func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool, tagSlugs string) (models.PlaintextSecretResult, error) {
func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool, tagSlugs string, expandSecretReferences bool) (models.PlaintextSecretResult, error) {
httpClient := resty.New()
httpClient.SetAuthToken(accessToken).
SetHeader("Accept", "application/json")
getSecretsRequest := api.GetRawSecretsV3Request{
WorkspaceId: workspaceId,
Environment: environmentName,
IncludeImport: includeImports,
Recursive: recursive,
TagSlugs: tagSlugs,
WorkspaceId: workspaceId,
Environment: environmentName,
IncludeImport: includeImports,
Recursive: recursive,
TagSlugs: tagSlugs,
ExpandSecretReferences: expandSecretReferences,
}
if secretsPath != "" {
@@ -104,7 +104,7 @@ func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentNa
plainTextSecrets := []models.SingleEnvironmentVariable{}
for _, secret := range rawSecrets.Secrets {
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue, Type: secret.Type, WorkspaceId: secret.Workspace})
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue, Type: secret.Type, WorkspaceId: secret.Workspace, SecretPath: secret.SecretPath})
}
if includeImports {
@@ -145,6 +145,7 @@ func GetSinglePlainTextSecretByNameV3(accessToken string, workspaceId string, en
Type: rawSecret.Secret.Type,
ID: rawSecret.Secret.ID,
Comment: rawSecret.Secret.SecretComment,
SecretPath: rawSecret.Secret.SecretPath,
}
return formattedSecrets, rawSecret.ETag, nil
@@ -283,7 +284,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
}
res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JTWToken, infisicalDotJson.WorkspaceId,
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs, true)
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", err)
if err == nil {
@@ -312,7 +313,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
} else {
if params.InfisicalToken != "" {
log.Debug().Msg("Trying to fetch secrets using service token")
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs, params.ExpandSecretReferences)
} else if params.UniversalAuthAccessToken != "" {
if params.WorkspaceId == "" {
@@ -320,7 +321,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
}
log.Debug().Msg("Trying to fetch secrets using universal auth")
res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)
res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs, params.ExpandSecretReferences)
errorToReturn = err
secretsToReturn = res.Secrets
@@ -330,44 +331,6 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
return secretsToReturn, errorToReturn
}
var secRefRegex = regexp.MustCompile(`\${([^\}]*)}`)
func recursivelyExpandSecret(expandedSecs map[string]string, interpolatedSecs map[string]string, crossSecRefFetch func(env string, path []string, key string) string, key string) string {
if v, ok := expandedSecs[key]; ok {
return v
}
interpolatedVal, ok := interpolatedSecs[key]
if !ok {
HandleError(fmt.Errorf("could not find refered secret - %s", key), "Kindly check whether its provided")
}
refs := secRefRegex.FindAllStringSubmatch(interpolatedVal, -1)
for _, val := range refs {
// key: "${something}" val: [${something},something]
interpolatedExp, interpolationKey := val[0], val[1]
ref := strings.Split(interpolationKey, ".")
// ${KEY1} => [key1]
if len(ref) == 1 {
val := recursivelyExpandSecret(expandedSecs, interpolatedSecs, crossSecRefFetch, interpolationKey)
interpolatedVal = strings.ReplaceAll(interpolatedVal, interpolatedExp, val)
continue
}
// cross board reference ${env.folder.key1} => [env folder key1]
if len(ref) > 1 {
secEnv, tmpSecPath, secKey := ref[0], ref[1:len(ref)-1], ref[len(ref)-1]
interpolatedSecs[interpolationKey] = crossSecRefFetch(secEnv, tmpSecPath, secKey) // get the reference value
val := recursivelyExpandSecret(expandedSecs, interpolatedSecs, crossSecRefFetch, interpolationKey)
interpolatedVal = strings.ReplaceAll(interpolatedVal, interpolatedExp, val)
}
}
expandedSecs[key] = interpolatedVal
return interpolatedVal
}
func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]models.SingleEnvironmentVariable {
secretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
@@ -378,70 +341,6 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
return secretMapByName
}
func ExpandSecrets(secrets []models.SingleEnvironmentVariable, auth models.ExpandSecretsAuthentication, projectConfigPathDir string) []models.SingleEnvironmentVariable {
expandedSecs := make(map[string]string)
interpolatedSecs := make(map[string]string)
// map[env.secret-path][keyname]Secret
crossEnvRefSecs := make(map[string]map[string]models.SingleEnvironmentVariable) // a cache to hold all cross board reference secrets
for _, sec := range secrets {
// get all references in a secret
refs := secRefRegex.FindAllStringSubmatch(sec.Value, -1)
// nil means its a secret without reference
if refs == nil {
expandedSecs[sec.Key] = sec.Value // atomic secrets without any interpolation
} else {
interpolatedSecs[sec.Key] = sec.Value
}
}
for i, sec := range secrets {
// already present pick that up
if expandedVal, ok := expandedSecs[sec.Key]; ok {
secrets[i].Value = expandedVal
continue
}
expandedVal := recursivelyExpandSecret(expandedSecs, interpolatedSecs, func(env string, secPaths []string, secKey string) string {
secPaths = append([]string{"/"}, secPaths...)
secPath := path.Join(secPaths...)
secPathDot := strings.Join(secPaths, ".")
uniqKey := fmt.Sprintf("%s.%s", env, secPathDot)
if crossRefSec, ok := crossEnvRefSecs[uniqKey]; !ok {
var refSecs []models.SingleEnvironmentVariable
var err error
// if not in cross reference cache, fetch it from server
if auth.InfisicalToken != "" {
refSecs, err = GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: env, InfisicalToken: auth.InfisicalToken, SecretsPath: secPath}, projectConfigPathDir)
} else if auth.UniversalAuthAccessToken != "" {
refSecs, err = GetAllEnvironmentVariables((models.GetAllSecretsParameters{Environment: env, UniversalAuthAccessToken: auth.UniversalAuthAccessToken, SecretsPath: secPath, WorkspaceId: sec.WorkspaceId}), projectConfigPathDir)
} else if IsLoggedIn() {
refSecs, err = GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: env, SecretsPath: secPath}, projectConfigPathDir)
} else {
HandleError(errors.New("no authentication provided"), "Please provide authentication to fetch secrets")
}
if err != nil {
HandleError(err, fmt.Sprintf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath), "If you are using a service token to fetch secrets, please ensure it is valid")
}
refSecsByKey := getSecretsByKeys(refSecs)
// save it to avoid calling api again for same environment and folder path
crossEnvRefSecs[uniqKey] = refSecsByKey
return refSecsByKey[secKey].Value
} else {
return crossRefSec[secKey].Value
}
}, sec.Key)
secrets[i].Value = expandedVal
}
return secrets
}
func OverrideSecrets(secrets []models.SingleEnvironmentVariable, secretType string) []models.SingleEnvironmentVariable {
personalSecrets := make(map[string]models.SingleEnvironmentVariable)
sharedSecrets := make(map[string]models.SingleEnvironmentVariable)

View File

@@ -1,5 +1,5 @@
{{- with secret "6553ccb2b7da580d7f6e7260" "dev" "/" }}
{{- with secret "8fac9f01-4a81-44d7-8ff0-3d7be684f56f" "staging" "/" `{"recursive":true, "expandSecretReferences": false}` }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -312,21 +312,37 @@ infisical agent --config example-agent-config-file.yaml
<Accordion title="listSecrets">
```bash
listSecrets "<project-id>" "environment-slug" "<secret-path>"
listSecrets "<project-id>" "environment-slug" "<secret-path>" "<optional-modifier>"
```
```bash example-template-usage
{{- with listSecrets "6553ccb2b7da580d7f6e7260" "dev" "/" }}
```bash example-template-usage-1
{{- with listSecrets "6553ccb2b7da580d7f6e7260" "dev" "/" `{"recursive": false, "expandSecretReferences": true}` }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
```bash example-template-usage-2
{{- with secret "da8056c8-01e2-4d24-b39f-cb4e004b8d44" "staging" "/" `{"recursive": true, "expandSecretReferences": true}` }}
{{- range . }}
{{- if eq .SecretPath "/"}}
{{ .Key }}={{ .Value }}
{{- else}}
{{ .SecretPath }}/{{ .Key }}={{ .Value }}
{{- end}}
{{- end }}
{{- end }}
```
**Function name**: listSecrets
**Description**: This function can be used to render the full list of secrets within a given project, environment and secret path.
**Description**: This function can be used to render the full list of secrets within a given project, environment and secret path.
**Returns**: A single secret object with the following keys `Key, WorkspaceId, Value, Type, ID, and Comment`
An optional JSON argument is also available. It includes the properties `recursive`, which defaults to false, and `expandSecretReferences`, which defaults to true and expands the returned secrets.
**Returns**: A single secret object with the following keys `Key, WorkspaceId, Value, SecretPath, Type, ID, and Comment`
</Accordion>