Merge pull request #2204 from Infisical/maidul-dig2urdy3

add single secret fetch for agent
This commit is contained in:
Maidul Islam
2024-07-30 20:09:32 -04:00
committed by GitHub
7 changed files with 139 additions and 9 deletions
+28
View File
@@ -434,6 +434,34 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
return getRawSecretsV3Response, nil return getRawSecretsV3Response, nil
} }
func CallFetchSingleSecretByName(httpClient *resty.Client, request GetRawSecretV3ByNameRequest) (GetRawSecretV3ByNameResponse, error) {
var getRawSecretV3ByNameResponse GetRawSecretV3ByNameResponse
response, err := httpClient.
R().
SetHeader("User-Agent", USER_AGENT).
SetResult(&getRawSecretV3ByNameResponse).
SetBody(request).
SetQueryParam("expandSecretReferences", "true").
SetQueryParam("include_imports", "true").
SetQueryParam("environment", request.Environment).
SetQueryParam("secretPath", request.SecretPath).
SetQueryParam("workspaceId", request.WorkspaceID).
SetQueryParam("type", "shared").
Get(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
if err != nil {
return GetRawSecretV3ByNameResponse{}, fmt.Errorf("CallFetchSingleSecretByName: Unable to complete api request [err=%w]", err)
}
if response.IsError() {
return GetRawSecretV3ByNameResponse{}, fmt.Errorf("CallFetchSingleSecretByName: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
}
getRawSecretV3ByNameResponse.ETag = response.Header().Get(("etag"))
return getRawSecretV3ByNameResponse, nil
}
func CallCreateDynamicSecretLeaseV1(httpClient *resty.Client, request CreateDynamicSecretLeaseV1Request) (CreateDynamicSecretLeaseV1Response, error) { func CallCreateDynamicSecretLeaseV1(httpClient *resty.Client, request CreateDynamicSecretLeaseV1Request) (CreateDynamicSecretLeaseV1Response, error) {
var createDynamicSecretLeaseResponse CreateDynamicSecretLeaseV1Response var createDynamicSecretLeaseResponse CreateDynamicSecretLeaseV1Response
response, err := httpClient. response, err := httpClient.
+22
View File
@@ -590,3 +590,25 @@ type GetRawSecretsV3Response struct {
Imports []ImportedRawSecretV3 `json:"imports"` Imports []ImportedRawSecretV3 `json:"imports"`
ETag string ETag string
} }
type GetRawSecretV3ByNameRequest struct {
SecretName string `json:"secretName"`
WorkspaceID string `json:"workspaceId"`
Type string `json:"type,omitempty"`
Environment string `json:"environment"`
SecretPath string `json:"secretPath,omitempty"`
}
type GetRawSecretV3ByNameResponse struct {
Secret struct {
ID string `json:"_id"`
Version int `json:"version"`
Workspace string `json:"workspace"`
Type string `json:"type"`
Environment string `json:"environment"`
SecretKey string `json:"secretKey"`
SecretValue string `json:"secretValue"`
SecretComment string `json:"secretComment"`
} `json:"secret"`
ETag string
}
+20 -2
View File
@@ -327,6 +327,21 @@ func secretTemplateFunction(accessToken string, existingEtag string, currentEtag
} }
} }
func getSingleSecretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string, string) (models.SingleEnvironmentVariable, error) {
return func(projectID, envSlug, secretPath, secretName string) (models.SingleEnvironmentVariable, error) {
secret, requestEtag, err := util.GetSinglePlainTextSecretByNameV3(accessToken, projectID, envSlug, secretPath, secretName)
if err != nil {
return models.SingleEnvironmentVariable{}, err
}
if existingEtag != requestEtag {
*currentEtag = requestEtag
}
return secret, nil
}
}
func dynamicSecretTemplateFunction(accessToken string, dynamicSecretManager *DynamicSecretLeaseManager, templateId int) func(...string) (map[string]interface{}, error) { func dynamicSecretTemplateFunction(accessToken string, dynamicSecretManager *DynamicSecretLeaseManager, templateId int) func(...string) (map[string]interface{}, error) {
return func(args ...string) (map[string]interface{}, error) { return func(args ...string) (map[string]interface{}, error) {
argLength := len(args) argLength := len(args)
@@ -358,9 +373,12 @@ func ProcessTemplate(templateId int, templatePath string, data interface{}, acce
// custom template function to fetch secrets from Infisical // custom template function to fetch secrets from Infisical
secretFunction := secretTemplateFunction(accessToken, existingEtag, currentEtag) secretFunction := secretTemplateFunction(accessToken, existingEtag, currentEtag)
dynamicSecretFunction := dynamicSecretTemplateFunction(accessToken, dynamicSecretManager, templateId) dynamicSecretFunction := dynamicSecretTemplateFunction(accessToken, dynamicSecretManager, templateId)
getSingleSecretFunction := getSingleSecretTemplateFunction(accessToken, existingEtag, currentEtag)
funcs := template.FuncMap{ funcs := template.FuncMap{
"secret": secretFunction, "secret": secretFunction, // depreciated
"dynamic_secret": dynamicSecretFunction, "listSecrets": secretFunction,
"dynamic_secret": dynamicSecretFunction,
"getSecretByName": getSingleSecretFunction,
"minus": func(a, b int) int { "minus": func(a, b int) int {
return a - b return a - b
}, },
+1
View File
@@ -35,6 +35,7 @@ type SingleEnvironmentVariable struct {
Workspace string `json:"workspace"` Workspace string `json:"workspace"`
} `json:"tags"` } `json:"tags"`
Comment string `json:"comment"` Comment string `json:"comment"`
Etag string `json:"Etag"`
} }
type PlaintextSecretResult struct { type PlaintextSecretResult struct {
+1 -1
View File
@@ -24,7 +24,7 @@ func ConvertPollingIntervalToTime(pollingInterval string) (time.Duration, error)
switch unit { switch unit {
case "s": case "s":
if number < 60 { if number < 60 {
return 0, fmt.Errorf("polling interval should be at least 60 seconds") return 0, fmt.Errorf("polling interval must be at least 60 seconds")
} }
return time.Duration(number) * time.Second, nil return time.Duration(number) * time.Second, nil
case "m": case "m":
+30
View File
@@ -118,6 +118,36 @@ func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentNa
}, nil }, nil
} }
func GetSinglePlainTextSecretByNameV3(accessToken string, workspaceId string, environmentName string, secretsPath string, secretName string) (models.SingleEnvironmentVariable, string, error) {
httpClient := resty.New()
httpClient.SetAuthToken(accessToken).
SetHeader("Accept", "application/json")
getSecretsRequest := api.GetRawSecretV3ByNameRequest{
WorkspaceID: workspaceId,
Environment: environmentName,
SecretName: secretName,
SecretPath: secretsPath,
}
rawSecret, err := api.CallFetchSingleSecretByName(httpClient, getSecretsRequest)
if err != nil {
return models.SingleEnvironmentVariable{}, "", err
}
formattedSecrets := models.SingleEnvironmentVariable{
Key: rawSecret.Secret.SecretKey,
WorkspaceId: rawSecret.Secret.Workspace,
Value: rawSecret.Secret.SecretValue,
Type: rawSecret.Secret.Type,
ID: rawSecret.Secret.ID,
Comment: rawSecret.Secret.SecretComment,
}
return formattedSecrets, rawSecret.ETag, nil
}
func CreateDynamicSecretLease(accessToken string, projectSlug string, environmentName string, secretsPath string, slug string, ttl string) (models.DynamicSecretLease, error) { func CreateDynamicSecretLease(accessToken string, projectSlug string, environmentName string, secretsPath string, slug string, ttl string) (models.DynamicSecretLease, error) {
httpClient := resty.New() httpClient := resty.New()
httpClient.SetAuthToken(accessToken). httpClient.SetAuthToken(accessToken).
@@ -252,7 +252,7 @@ To install the Infisical agent, you must first install the [Infisical CLI](../cl
Once you have the CLI installed, you will need to provision programmatic access for the agent via [Universal Auth](/documentation/platform/identities/universal-auth). To obtain a **Client ID** and a **Client Secret**, follow the step by step guide outlined [here](/documentation/platform/identities/universal-auth). Once you have the CLI installed, you will need to provision programmatic access for the agent via [Universal Auth](/documentation/platform/identities/universal-auth). To obtain a **Client ID** and a **Client Secret**, follow the step by step guide outlined [here](/documentation/platform/identities/universal-auth).
Next, create agent config file as shown below. Next, create agent config file as shown below. The example agent configuration file that defines the token authentication method, one sink location, and a secret template.
```yaml example-agent-config-file.yaml ```yaml example-agent-config-file.yaml
infisical: infisical:
@@ -277,8 +277,8 @@ templates:
command: ./reload-app.sh command: ./reload-app.sh
``` ```
Above is an example agent configuration file that defines the token authentication method, one sink location (where to deposit access tokens after renewal) and a secret template. The secret template below will be used to render the secrets with the key and the value separated by `=` sign. You'll notice that a custom function named `secret` is used to fetch the secrets.
This function takes the following arguments: `secret "<project-id>" "<environment-slug>" "<secret-path>"`.
```text my-dot-ev-secret-template ```text my-dot-ev-secret-template
{{- with secret "6553ccb2b7da580d7f6e7260" "dev" "/" }} {{- with secret "6553ccb2b7da580d7f6e7260" "dev" "/" }}
@@ -288,11 +288,42 @@ Above is an example agent configuration file that defines the token authenticati
{{- end }} {{- end }}
``` ```
The secret template above will be used to render the secrets where the key and the value are separated by `=` sign. You'll notice that a custom function named `secret` is used to fetch the secrets. After defining the agent configuration file, run the command below pointing to the path where the agent configuration file is located.
This function takes the following arguments: `secret "<project-id>" "<environment-slug>" "<secret-path>"`.
```bash ```bash
infisical agent --config example-agent-config-file.yaml infisical agent --config example-agent-config-file.yaml
``` ```
After defining the agent configuration file, run the command above pointing to the path where the agent configuration is located.
### Available secret template functions
<Accordion title="listSecrets">
```bash
listSecrets "<project-id>" "environment-slug" "<secret-path>"
```
```bash example-template-usage
{{- with listSecrets "6553ccb2b7da580d7f6e7260" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
This function can be used to render the full list of secrets within a given project, environment and secret path.
</Accordion>
<Accordion title="getSecretByName">
```bash
getSecretByName "<project-id>" "<environment-slug>" "<secret-path>" "<secret-name>"
```
```bash example-template-usage
{{ with getSecretByName "d821f21d-aa90-453b-8448-8c78c1160a0e" "dev" "/" "POSTHOG_HOST"}}
{{ if .Value }}
password = "{{ .Value }}"
{{ end }}
{{ end }}
```
This function can be used to render a single secret by it's name.
</Accordion>