Merge pull request #2123 from akhilmhdh/cli-operation-to-raw

Switched CLI and K8s operator to secret raw endpoint
This commit is contained in:
Maidul Islam
2024-07-20 13:19:13 -04:00
committed by GitHub
14 changed files with 172 additions and 989 deletions

View File

@@ -24,10 +24,10 @@ export const getBotKeyFnFactory = (
const bot = await projectBotDAL.findOne({ projectId: project.id });
if (!bot) throw new BadRequestError({ message: "Failed to find bot key" });
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" });
if (!bot) throw new BadRequestError({ message: "Failed to find bot key", name: "bot_not_found_error" });
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active", name: "bot_not_found_error" });
if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey)
throw new BadRequestError({ message: "Encryption key missing" });
throw new BadRequestError({ message: "Encryption key missing", name: "bot_not_found_error" });
const botPrivateKey = getBotPrivateKey({ bot });

View File

@@ -225,25 +225,6 @@ func CallIsAuthenticated(httpClient *resty.Client) bool {
return true
}
func CallGetAccessibleEnvironments(httpClient *resty.Client, request GetAccessibleEnvironmentsRequest) (GetAccessibleEnvironmentsResponse, error) {
var accessibleEnvironmentsResponse GetAccessibleEnvironmentsResponse
response, err := httpClient.
R().
SetResult(&accessibleEnvironmentsResponse).
SetHeader("User-Agent", USER_AGENT).
Get(fmt.Sprintf("%v/v2/workspace/%s/environments", config.INFISICAL_URL, request.WorkspaceId))
if err != nil {
return GetAccessibleEnvironmentsResponse{}, err
}
if response.IsError() {
return GetAccessibleEnvironmentsResponse{}, fmt.Errorf("CallGetAccessibleEnvironments: Unsuccessful response: [response=%v] [response-code=%v] [url=%s]", response, response.StatusCode(), response.Request.URL)
}
return accessibleEnvironmentsResponse, nil
}
func CallGetNewAccessTokenWithRefreshToken(httpClient *resty.Client, refreshToken string) (GetNewAccessTokenWithRefreshTokenResponse, error) {
var newAccessToken GetNewAccessTokenWithRefreshTokenResponse
response, err := httpClient.
@@ -267,45 +248,6 @@ func CallGetNewAccessTokenWithRefreshToken(httpClient *resty.Client, refreshToke
return newAccessToken, nil
}
func CallGetSecretsV3(httpClient *resty.Client, request GetEncryptedSecretsV3Request) (GetEncryptedSecretsV3Response, error) {
var secretsResponse GetEncryptedSecretsV3Response
httpRequest := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetQueryParam("environment", request.Environment).
SetQueryParam("workspaceId", request.WorkspaceId)
if request.Recursive {
httpRequest.SetQueryParam("recursive", "true")
}
if request.IncludeImport {
httpRequest.SetQueryParam("include_imports", "true")
}
if request.SecretPath != "" {
httpRequest.SetQueryParam("secretPath", request.SecretPath)
}
response, err := httpRequest.Get(fmt.Sprintf("%v/v3/secrets", config.INFISICAL_URL))
if err != nil {
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
if response.StatusCode() == 401 {
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Request to access secrets with [environment=%v] [path=%v] [workspaceId=%v] is denied. Please check if your authentication method has access to requested scope", request.Environment, request.SecretPath, request.WorkspaceId)
} else {
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%v]", response.RawResponse)
}
}
return secretsResponse, nil
}
func CallGetFoldersV1(httpClient *resty.Client, request GetFoldersV1Request) (GetFoldersV1Response, error) {
var foldersResponse GetFoldersV1Response
httpRequest := httpClient.
@@ -370,27 +312,7 @@ func CallDeleteFolderV1(httpClient *resty.Client, request DeleteFolderV1Request)
return folderResponse, nil
}
func CallCreateSecretsV3(httpClient *resty.Client, request CreateSecretV3Request) error {
var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
if err != nil {
return fmt.Errorf("CallCreateSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallCreateSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
return nil
}
func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request) error {
func CallDeleteSecretsRawV3(httpClient *resty.Client, request DeleteSecretV3Request) error {
var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient.
@@ -398,7 +320,7 @@ func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Delete(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
Delete(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
if err != nil {
return fmt.Errorf("CallDeleteSecretsV3: Unable to complete api request [err=%s]", err)
@@ -411,46 +333,6 @@ func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request
return nil
}
func CallUpdateSecretsV3(httpClient *resty.Client, request UpdateSecretByNameV3Request, secretName string) error {
var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Patch(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, secretName))
if err != nil {
return fmt.Errorf("CallUpdateSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallUpdateSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
return nil
}
func CallGetSingleSecretByNameV3(httpClient *resty.Client, request CreateSecretV3Request) error {
var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
if err != nil {
return fmt.Errorf("CallGetSingleSecretByNameV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallGetSingleSecretByNameV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
return nil
}
func CallCreateServiceToken(httpClient *resty.Client, request CreateServiceTokenRequest) (CreateServiceTokenResponse, error) {
var createServiceTokenResponse CreateServiceTokenResponse
response, err := httpClient.
@@ -535,8 +417,12 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unable to complete api request [err=%w]", err)
}
if response.IsError() && strings.Contains(response.String(), "bot_not_found_error") {
return GetRawSecretsV3Response{}, fmt.Errorf("project with id %s is a legacy project type, please navigate to project settings and disable end to end encryption then try again", request.WorkspaceId)
if response.IsError() &&
(strings.Contains(response.String(), "bot_not_found_error") ||
strings.Contains(strings.ToLower(response.String()), "failed to find bot key") ||
strings.Contains(strings.ToLower(response.String()), "bot is not active")) {
return GetRawSecretsV3Response{}, fmt.Errorf(`Project with id %s is incompatible with your current CLI version. Upgrade your project by visiting the project settings page. If you're self hosting and project upgrade option isn't yet available, contact your administrator to upgrade your Infisical instance to the latest release.
`, request.WorkspaceId)
}
if response.IsError() {

View File

@@ -312,7 +312,7 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
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.GetPlainTextSecretsViaMachineIdentity(accessToken, projectID, envSlug, secretPath, false, false)
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false)
if err != nil {
return nil, err
}

View File

@@ -187,12 +187,34 @@ var secretsSetCmd = &cobra.Command{
var secretOperations []models.SecretSetOperation
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
if projectId == "" {
util.PrintErrorMessageAndExit("When using service tokens or machine identities, you must set the --projectId flag")
}
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, token)
} else {
util.RequireLogin()
util.RequireLocalWorkspaceFile()
if projectId == "" {
workspaceFile, err := util.GetWorkSpaceFromFile()
if err != nil {
util.HandleError(err, "unable to get your local config details [err=%v]")
}
secretOperations, err = util.SetEncryptedSecrets(args, secretType, environmentName, secretsPath)
projectId = workspaceFile.WorkspaceId
}
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
if err != nil {
util.HandleError(err, "unable to authenticate [err=%v]")
}
if loggedInUserDetails.LoginExpired {
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
}
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{
Type: "",
Token: loggedInUserDetails.UserCredentials.JTWToken,
})
}
if err != nil {
@@ -285,7 +307,7 @@ var secretsDeleteCmd = &cobra.Command{
SecretPath: secretsPath,
}
err = api.CallDeleteSecretsV3(httpClient, request)
err = api.CallDeleteSecretsRawV3(httpClient, request)
if err != nil {
util.HandleError(err, "Unable to complete your delete request")
}

View File

@@ -140,3 +140,10 @@ type SecretSetOperation struct {
SecretValue string
SecretOperation string
}
type BackupSecretKeyRing struct {
ProjectID string `json:"projectId"`
Environment string `json:"environment"`
SecretPath string `json:"secretPath"`
Secrets []SingleEnvironmentVariable
}

View File

@@ -244,10 +244,5 @@ func WriteConfigFile(configFile *models.ConfigFile) error {
return fmt.Errorf("writeConfigFile: Unable to write to file [err=%s]", err)
}
if err != nil {
return fmt.Errorf("writeConfigFile: unable to write config file because an error occurred when write the config to file [err=%s]", err)
}
return nil
}

View File

@@ -33,6 +33,8 @@ const (
SERVICE_TOKEN_IDENTIFIER = "service-token"
UNIVERSAL_AUTH_TOKEN_IDENTIFIER = "universal-auth-token"
INFISICAL_BACKUP_SECRET = "infisical-backup-secrets"
)
var (

View File

@@ -52,10 +52,6 @@ func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentia
return models.UserCredentials{}, fmt.Errorf("getUserCredsFromKeyRing: Something went wrong when unmarshalling user creds [err=%s]", err)
}
if err != nil {
return models.UserCredentials{}, fmt.Errorf("GetUserCredsFromKeyRing: Unable to store user credentials [err=%s]", err)
}
return userCredentials, err
}

View File

@@ -1,7 +1,6 @@
package util
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
@@ -9,6 +8,7 @@ import (
"os"
"path"
"regexp"
"slices"
"strings"
"unicode"
@@ -17,12 +17,13 @@ import (
"github.com/Infisical/infisical-merge/packages/models"
"github.com/go-resty/resty/v2"
"github.com/rs/zerolog/log"
"github.com/zalando/go-keyring"
)
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, api.GetServiceTokenDetailsResponse, error) {
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
}
serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
@@ -34,19 +35,19 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
if err != nil {
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
return nil, fmt.Errorf("unable to get service token details. [err=%v]", err)
}
// if multiple scopes are there then user needs to specify which environment and secret path
if environment == "" {
if len(serviceTokenDetails.Scopes) != 1 {
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("you need to provide the --env for multiple environment scoped token")
return nil, fmt.Errorf("you need to provide the --env for multiple environment scoped token")
} else {
environment = serviceTokenDetails.Scopes[0].Environment
}
}
encryptedSecrets, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, api.GetRawSecretsV3Request{
WorkspaceId: serviceTokenDetails.Workspace,
Environment: environment,
SecretPath: secretPath,
@@ -54,109 +55,28 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
Recursive: recursive,
})
if err != nil {
return nil, api.GetServiceTokenDetailsResponse{}, err
}
decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag)
if err != nil {
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
}
plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV)
if err != nil {
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decrypt the required workspace key")
}
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets.Secrets)
if err != nil {
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
}
if includeImports {
plainTextSecrets, err = InjectImportedSecret(plainTextWorkspaceKey, plainTextSecrets, encryptedSecrets.ImportedSecrets)
if err != nil {
return nil, api.GetServiceTokenDetailsResponse{}, err
}
}
return plainTextSecrets, serviceTokenDetails, nil
}
func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string, tagSlugs string, secretsPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, error) {
httpClient := resty.New()
httpClient.SetAuthToken(JTWToken).
SetHeader("Accept", "application/json")
request := api.GetEncryptedWorkspaceKeyRequest{
WorkspaceId: workspaceId,
}
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
if err != nil {
return nil, fmt.Errorf("unable to get your encrypted workspace key. [err=%v]", err)
}
encryptedWorkspaceKey, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
if err != nil {
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKey")
}
encryptedWorkspaceKeySenderPublicKey, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
if err != nil {
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKeySenderPublicKey")
}
encryptedWorkspaceKeyNonce, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
if err != nil {
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKeyNonce")
}
currentUsersPrivateKey, err := base64.StdEncoding.DecodeString(receiversPrivateKey)
if err != nil {
HandleError(err, "Unable to get bytes represented by the base64 for currentUsersPrivateKey")
}
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
}
plainTextWorkspaceKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
getSecretsRequest := api.GetEncryptedSecretsV3Request{
WorkspaceId: workspaceId,
Environment: environmentName,
IncludeImport: includeImports,
Recursive: recursive,
// TagSlugs: tagSlugs,
}
if secretsPath != "" {
getSecretsRequest.SecretPath = secretsPath
}
encryptedSecrets, err := api.CallGetSecretsV3(httpClient, getSecretsRequest)
if err != nil {
return nil, err
}
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets.Secrets)
if err != nil {
return nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
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})
}
if includeImports {
plainTextSecrets, err = InjectImportedSecret(plainTextWorkspaceKey, plainTextSecrets, encryptedSecrets.ImportedSecrets)
plainTextSecrets, err = InjectRawImportedSecret(plainTextSecrets, rawSecrets.Imports)
if err != nil {
return nil, err
}
}
return plainTextSecrets, nil
}
func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool) (models.PlaintextSecretResult, error) {
func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool) (models.PlaintextSecretResult, error) {
httpClient := resty.New()
httpClient.SetAuthToken(accessToken).
SetHeader("Accept", "application/json")
@@ -180,9 +100,6 @@ func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId strin
}
plainTextSecrets := []models.SingleEnvironmentVariable{}
if err != nil {
return models.PlaintextSecretResult{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
}
for _, secret := range rawSecrets.Secrets {
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue, Type: secret.Type, WorkspaceId: secret.Workspace})
@@ -226,34 +143,6 @@ func CreateDynamicSecretLease(accessToken string, projectSlug string, environmen
}, nil
}
func InjectImportedSecret(plainTextWorkspaceKey []byte, secrets []models.SingleEnvironmentVariable, importedSecrets []api.ImportedSecretV3) ([]models.SingleEnvironmentVariable, error) {
if importedSecrets == nil {
return secrets, nil
}
hasOverriden := make(map[string]bool)
for _, sec := range secrets {
hasOverriden[sec.Key] = true
}
for i := len(importedSecrets) - 1; i >= 0; i-- {
importSec := importedSecrets[i]
plainTextImportedSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, importSec.Secrets)
if err != nil {
return nil, fmt.Errorf("unable to decrypt your imported secrets [err=%v]", err)
}
for _, sec := range plainTextImportedSecrets {
if _, ok := hasOverriden[sec.Key]; !ok {
secrets = append(secrets, sec)
hasOverriden[sec.Key] = true
}
}
}
return secrets, nil
}
func InjectRawImportedSecret(secrets []models.SingleEnvironmentVariable, importedSecrets []api.ImportedRawSecretV3) ([]models.SingleEnvironmentVariable, error) {
if importedSecrets == nil {
return secrets, nil
@@ -361,18 +250,19 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
infisicalDotJson.WorkspaceId = params.WorkspaceId
}
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, infisicalDotJson.WorkspaceId,
params.Environment, params.TagSlugs, params.SecretsPath, params.IncludeImport, params.Recursive)
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JTWToken, infisicalDotJson.WorkspaceId,
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", err)
backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32]
if errorToReturn == nil {
WriteBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath, backupSecretsEncryptionKey, secretsToReturn)
if err == nil {
WriteBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath, res.Secrets)
}
secretsToReturn = res.Secrets
errorToReturn = err
// only attempt to serve cached secrets if no internet connection and if at least one secret cached
if !isConnected {
backedSecrets, err := ReadBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath, backupSecretsEncryptionKey)
backedSecrets, err := ReadBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath)
if len(backedSecrets) > 0 {
PrintWarning("Unable to fetch latest secret(s) due to connection error, serving secrets from last successful fetch. For more info, run with --debug")
secretsToReturn = backedSecrets
@@ -383,7 +273,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)
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
} else if params.UniversalAuthAccessToken != "" {
if params.WorkspaceId == "" {
@@ -391,7 +281,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
}
log.Debug().Msg("Trying to fetch secrets using universal auth")
res, err := GetPlainTextSecretsViaMachineIdentity(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
errorToReturn = err
secretsToReturn = res.Secrets
@@ -556,174 +446,71 @@ func OverrideSecrets(secrets []models.SingleEnvironmentVariable, secretType stri
return secretsToReturn
}
func GetPlainTextSecrets(key []byte, encryptedSecrets []api.EncryptedSecretV3) ([]models.SingleEnvironmentVariable, error) {
plainTextSecrets := []models.SingleEnvironmentVariable{}
for _, secret := range encryptedSecrets {
// Decrypt key
key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV)
if err != nil {
return nil, fmt.Errorf("unable to decode secret IV for secret key")
}
key_tag, err := base64.StdEncoding.DecodeString(secret.SecretKeyTag)
if err != nil {
return nil, fmt.Errorf("unable to decode secret authentication tag for secret key")
}
key_ciphertext, err := base64.StdEncoding.DecodeString(secret.SecretKeyCiphertext)
if err != nil {
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
}
plainTextKey, err := crypto.DecryptSymmetric(key, key_ciphertext, key_tag, key_iv)
if err != nil {
return nil, fmt.Errorf("unable to symmetrically decrypt secret key")
}
// Decrypt value
value_iv, err := base64.StdEncoding.DecodeString(secret.SecretValueIV)
if err != nil {
return nil, fmt.Errorf("unable to decode secret IV for secret value")
}
value_tag, err := base64.StdEncoding.DecodeString(secret.SecretValueTag)
if err != nil {
return nil, fmt.Errorf("unable to decode secret authentication tag for secret value")
}
value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValueCiphertext)
if err != nil {
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
}
plainTextValue, err := crypto.DecryptSymmetric(key, value_ciphertext, value_tag, value_iv)
if err != nil {
return nil, fmt.Errorf("unable to symmetrically decrypt secret value")
}
// Decrypt comment
comment_iv, err := base64.StdEncoding.DecodeString(secret.SecretCommentIV)
if err != nil {
return nil, fmt.Errorf("unable to decode secret IV for secret value")
}
comment_tag, err := base64.StdEncoding.DecodeString(secret.SecretCommentTag)
if err != nil {
return nil, fmt.Errorf("unable to decode secret authentication tag for secret value")
}
comment_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretCommentCiphertext)
if err != nil {
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
}
plainTextComment, err := crypto.DecryptSymmetric(key, comment_ciphertext, comment_tag, comment_iv)
if err != nil {
return nil, fmt.Errorf("unable to symmetrically decrypt secret comment")
}
plainTextSecret := models.SingleEnvironmentVariable{
Key: string(plainTextKey),
Value: string(plainTextValue),
Type: string(secret.Type),
ID: secret.ID,
Tags: secret.Tags,
Comment: string(plainTextComment),
}
plainTextSecrets = append(plainTextSecrets, plainTextSecret)
}
return plainTextSecrets, nil
}
func WriteBackupSecrets(workspace string, environment string, secretsPath string, encryptionKey []byte, secrets []models.SingleEnvironmentVariable) error {
formattedPath := strings.ReplaceAll(secretsPath, "/", "-")
fileName := fmt.Sprintf("secrets_%s_%s_%s", workspace, environment, formattedPath)
secrets_backup_folder_name := "secrets-backup"
_, fullConfigFileDirPath, err := GetFullConfigFilePath()
func WriteBackupSecrets(workspace string, environment string, secretsPath string, secrets []models.SingleEnvironmentVariable) error {
var backedUpSecrets []models.BackupSecretKeyRing
secretValueInKeyRing, err := GetValueInKeyring(INFISICAL_BACKUP_SECRET)
if err != nil {
return fmt.Errorf("WriteBackupSecrets: unable to get full config folder path [err=%s]", err)
}
// create secrets backup directory
fullPathToSecretsBackupFolder := fmt.Sprintf("%s/%s", fullConfigFileDirPath, secrets_backup_folder_name)
if _, err := os.Stat(fullPathToSecretsBackupFolder); errors.Is(err, os.ErrNotExist) {
err := os.Mkdir(fullPathToSecretsBackupFolder, os.ModePerm)
if err != nil {
return err
if err == keyring.ErrUnsupportedPlatform {
return errors.New("your OS does not support keyring. Consider using a service token https://infisical.com/docs/documentation/platform/token")
} else if err != keyring.ErrNotFound {
return fmt.Errorf("something went wrong, failed to retrieve value from system keyring [error=%v]", err)
}
}
_ = json.Unmarshal([]byte(secretValueInKeyRing), &backedUpSecrets)
var encryptedSecrets []models.SymmetricEncryptionResult
for _, secret := range secrets {
marshaledSecrets, _ := json.Marshal(secret)
result, err := crypto.EncryptSymmetric(marshaledSecrets, encryptionKey)
if err != nil {
return err
}
encryptedSecrets = append(encryptedSecrets, result)
backedUpSecrets = slices.DeleteFunc(backedUpSecrets, func(e models.BackupSecretKeyRing) bool {
return e.SecretPath == secretsPath && e.ProjectID == workspace && e.Environment == environment
})
newBackupSecret := models.BackupSecretKeyRing{
ProjectID: workspace,
Environment: environment,
SecretPath: secretsPath,
Secrets: secrets,
}
backedUpSecrets = append(backedUpSecrets, newBackupSecret)
listOfSecretsMarshalled, _ := json.Marshal(encryptedSecrets)
err = os.WriteFile(fmt.Sprintf("%s/%s", fullPathToSecretsBackupFolder, fileName), listOfSecretsMarshalled, 0600)
listOfSecretsMarshalled, err := json.Marshal(backedUpSecrets)
if err != nil {
return fmt.Errorf("WriteBackupSecrets: Unable to write backup secrets to file [err=%s]", err)
return err
}
err = SetValueInKeyring(INFISICAL_BACKUP_SECRET, string(listOfSecretsMarshalled))
if err != nil {
return fmt.Errorf("StoreUserCredsInKeyRing: unable to store user credentials because [err=%s]", err)
}
return nil
}
func ReadBackupSecrets(workspace string, environment string, secretsPath string, encryptionKey []byte) ([]models.SingleEnvironmentVariable, error) {
formattedPath := strings.ReplaceAll(secretsPath, "/", "-")
fileName := fmt.Sprintf("secrets_%s_%s_%s", workspace, environment, formattedPath)
secrets_backup_folder_name := "secrets-backup"
_, fullConfigFileDirPath, err := GetFullConfigFilePath()
func ReadBackupSecrets(workspace string, environment string, secretsPath string) ([]models.SingleEnvironmentVariable, error) {
secretValueInKeyRing, err := GetValueInKeyring(INFISICAL_BACKUP_SECRET)
if err != nil {
return nil, fmt.Errorf("ReadBackupSecrets: unable to write config file because an error occurred when getting config file path [err=%s]", err)
if err == keyring.ErrUnsupportedPlatform {
return nil, errors.New("your OS does not support keyring. Consider using a service token https://infisical.com/docs/documentation/platform/token")
} else if err == keyring.ErrNotFound {
return nil, errors.New("credentials not found in system keyring")
} else {
return nil, fmt.Errorf("something went wrong, failed to retrieve value from system keyring [error=%v]", err)
}
}
fullPathToSecretsBackupFolder := fmt.Sprintf("%s/%s", fullConfigFileDirPath, secrets_backup_folder_name)
if _, err := os.Stat(fullPathToSecretsBackupFolder); errors.Is(err, os.ErrNotExist) {
return nil, nil
}
encryptedBackupSecretsFilePath := fmt.Sprintf("%s/%s", fullPathToSecretsBackupFolder, fileName)
encryptedBackupSecretsAsBytes, err := os.ReadFile(encryptedBackupSecretsFilePath)
var backedUpSecrets []models.BackupSecretKeyRing
err = json.Unmarshal([]byte(secretValueInKeyRing), &backedUpSecrets)
if err != nil {
return nil, err
return nil, fmt.Errorf("getUserCredsFromKeyRing: Something went wrong when unmarshalling user creds [err=%s]", err)
}
var listOfEncryptedBackupSecrets []models.SymmetricEncryptionResult
_ = json.Unmarshal(encryptedBackupSecretsAsBytes, &listOfEncryptedBackupSecrets)
var plainTextSecrets []models.SingleEnvironmentVariable
for _, encryptedSecret := range listOfEncryptedBackupSecrets {
result, err := crypto.DecryptSymmetric(encryptionKey, encryptedSecret.CipherText, encryptedSecret.AuthTag, encryptedSecret.Nonce)
if err != nil {
return nil, err
for _, backupSecret := range backedUpSecrets {
if backupSecret.Environment == environment && backupSecret.ProjectID == workspace && backupSecret.SecretPath == secretsPath {
return backupSecret.Secrets, nil
}
var plainTextSecret models.SingleEnvironmentVariable
err = json.Unmarshal(result, &plainTextSecret)
if err != nil {
return nil, err
}
plainTextSecrets = append(plainTextSecrets, plainTextSecret)
}
return plainTextSecrets, nil
return nil, nil
}
func DeleteBackupSecrets() error {
// keeping this logic for now. Need to remove it later as more users migrate keyring would be used and this folder will be removed completely by then
secrets_backup_folder_name := "secrets-backup"
_, fullConfigFileDirPath, err := GetFullConfigFilePath()
@@ -733,6 +520,8 @@ func DeleteBackupSecrets() error {
fullPathToSecretsBackupFolder := fmt.Sprintf("%s/%s", fullConfigFileDirPath, secrets_backup_folder_name)
DeleteValueInKeyring(INFISICAL_BACKUP_SECRET)
return os.RemoveAll(fullPathToSecretsBackupFolder)
}
@@ -809,200 +598,6 @@ func GetPlainTextWorkspaceKey(authenticationToken string, receiverPrivateKey str
return crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey), nil
}
func SetEncryptedSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string) ([]models.SecretSetOperation, error) {
workspaceFile, err := GetWorkSpaceFromFile()
if err != nil {
return nil, fmt.Errorf("unable to get your local config details [err=%v]", err)
}
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
if err != nil {
return nil, fmt.Errorf("unable to authenticate [err=%v]", err)
}
if loggedInUserDetails.LoginExpired {
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
}
httpClient := resty.New().
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
SetHeader("Accept", "application/json")
request := api.GetEncryptedWorkspaceKeyRequest{
WorkspaceId: workspaceFile.WorkspaceId,
}
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
if err != nil {
return nil, fmt.Errorf("unable to get your encrypted workspace key [err=%v]", err)
}
encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey)
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
}
// decrypt workspace key
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
infisicalTokenEnv := os.Getenv(INFISICAL_TOKEN_NAME)
// pull current secrets
secrets, err := GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, InfisicalToken: infisicalTokenEnv}, "")
if err != nil {
return nil, fmt.Errorf("unable to retrieve secrets [err=%v]", err)
}
secretsToCreate := []api.Secret{}
secretsToModify := []api.Secret{}
secretOperations := []models.SecretSetOperation{}
sharedSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
personalSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
for _, secret := range secrets {
if secret.Type == SECRET_TYPE_PERSONAL {
personalSecretMapByName[secret.Key] = secret
} else {
sharedSecretMapByName[secret.Key] = secret
}
}
for _, arg := range secretArgs {
splitKeyValueFromArg := strings.SplitN(arg, "=", 2)
if splitKeyValueFromArg[0] == "" || splitKeyValueFromArg[1] == "" {
PrintErrorMessageAndExit("ensure that each secret has a none empty key and value. Modify the input and try again")
}
if unicode.IsNumber(rune(splitKeyValueFromArg[0][0])) {
PrintErrorMessageAndExit("keys of secrets cannot start with a number. Modify the key name(s) and try again")
}
// Key and value from argument
key := strings.TrimSpace(splitKeyValueFromArg[0])
value := splitKeyValueFromArg[1]
hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key)))
encryptedKey, err := crypto.EncryptSymmetric([]byte(key), []byte(plainTextEncryptionKey))
if err != nil {
return nil, fmt.Errorf("unable to encrypt your secrets [err=%v]", err)
}
hashedValue := fmt.Sprintf("%x", sha256.Sum256([]byte(value)))
encryptedValue, err := crypto.EncryptSymmetric([]byte(value), []byte(plainTextEncryptionKey))
if err != nil {
return nil, fmt.Errorf("unable to encrypt your secrets [err=%v]", err)
}
var existingSecret models.SingleEnvironmentVariable
var doesSecretExist bool
if secretType == SECRET_TYPE_SHARED {
existingSecret, doesSecretExist = sharedSecretMapByName[key]
} else {
existingSecret, doesSecretExist = personalSecretMapByName[key]
}
if doesSecretExist {
// case: secret exists in project so it needs to be modified
encryptedSecretDetails := api.Secret{
ID: existingSecret.ID,
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
SecretValueHash: hashedValue,
PlainTextKey: key,
Type: existingSecret.Type,
}
// Only add to modifications if the value is different
if existingSecret.Value != value {
secretsToModify = append(secretsToModify, encryptedSecretDetails)
secretOperations = append(secretOperations, models.SecretSetOperation{
SecretKey: key,
SecretValue: value,
SecretOperation: "SECRET VALUE MODIFIED",
})
} else {
// Current value is same as exisitng so no change
secretOperations = append(secretOperations, models.SecretSetOperation{
SecretKey: key,
SecretValue: value,
SecretOperation: "SECRET VALUE UNCHANGED",
})
}
} else {
// case: secret doesn't exist in project so it needs to be created
encryptedSecretDetails := api.Secret{
SecretKeyCiphertext: base64.StdEncoding.EncodeToString(encryptedKey.CipherText),
SecretKeyIV: base64.StdEncoding.EncodeToString(encryptedKey.Nonce),
SecretKeyTag: base64.StdEncoding.EncodeToString(encryptedKey.AuthTag),
SecretKeyHash: hashedKey,
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
SecretValueHash: hashedValue,
Type: secretType,
PlainTextKey: key,
}
secretsToCreate = append(secretsToCreate, encryptedSecretDetails)
secretOperations = append(secretOperations, models.SecretSetOperation{
SecretKey: key,
SecretValue: value,
SecretOperation: "SECRET CREATED",
})
}
}
for _, secret := range secretsToCreate {
createSecretRequest := api.CreateSecretV3Request{
WorkspaceID: workspaceFile.WorkspaceId,
Environment: environmentName,
SecretName: secret.PlainTextKey,
SecretKeyCiphertext: secret.SecretKeyCiphertext,
SecretKeyIV: secret.SecretKeyIV,
SecretKeyTag: secret.SecretKeyTag,
SecretValueCiphertext: secret.SecretValueCiphertext,
SecretValueIV: secret.SecretValueIV,
SecretValueTag: secret.SecretValueTag,
Type: secret.Type,
SecretPath: secretsPath,
}
err = api.CallCreateSecretsV3(httpClient, createSecretRequest)
if err != nil {
return nil, fmt.Errorf("unable to process new secret creations [err=%v]", err)
}
}
for _, secret := range secretsToModify {
updateSecretRequest := api.UpdateSecretByNameV3Request{
WorkspaceID: workspaceFile.WorkspaceId,
Environment: environmentName,
SecretValueCiphertext: secret.SecretValueCiphertext,
SecretValueIV: secret.SecretValueIV,
SecretValueTag: secret.SecretValueTag,
Type: secret.Type,
SecretPath: secretsPath,
}
err = api.CallUpdateSecretsV3(httpClient, updateSecretRequest, secret.PlainTextKey)
if err != nil {
return nil, fmt.Errorf("unable to process secret update request [err=%v]", err)
}
}
return secretOperations, nil
}
func SetRawSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string, projectId string, tokenDetails *models.TokenDetails) ([]models.SecretSetOperation, error) {
if tokenDetails == nil {
@@ -1012,7 +607,9 @@ func SetRawSecrets(secretArgs []string, secretType string, environmentName strin
getAllEnvironmentVariablesRequest := models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, WorkspaceId: projectId}
if tokenDetails.Type == UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
getAllEnvironmentVariablesRequest.UniversalAuthAccessToken = tokenDetails.Token
} else {
}
if tokenDetails.Type == SERVICE_TOKEN_IDENTIFIER {
getAllEnvironmentVariablesRequest.InfisicalToken = tokenDetails.Token
}

View File

@@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.6.5
version: v0.7.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v0.6.5"
appVersion: "v0.7.0"

View File

@@ -40,6 +40,10 @@ func (r *InfisicalSecretReconciler) HandleAuthentication(ctx context.Context, in
if err != nil {
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
}
if infisicalToken != "" {
infisicalClient.Auth().SetAccessToken(infisicalToken)
return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_TOKEN}, nil
}
// ? Legacy support, service account auth
serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
@@ -48,9 +52,8 @@ func (r *InfisicalSecretReconciler) HandleAuthentication(ctx context.Context, in
}
if serviceAccountCreds.AccessKey != "" || serviceAccountCreds.PrivateKey != "" || serviceAccountCreds.PublicKey != "" {
infisicalClient.Auth().SetAccessToken(serviceAccountCreds.AccessKey)
return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_ACCOUNT}, nil
} else if infisicalToken != "" {
return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_TOKEN}, nil
}
authStrategies := map[AuthStrategyType]func(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error){
@@ -69,7 +72,7 @@ func (r *InfisicalSecretReconciler) HandleAuthentication(ctx context.Context, in
return authDetails, nil
}
if err != nil && !errors.Is(err, ErrAuthNotApplicable) {
if !errors.Is(err, ErrAuthNotApplicable) {
return AuthenticationDetails{}, fmt.Errorf("authentication failed for strategy [%s] [err=%w]", authStrategy, err)
}
}
@@ -360,11 +363,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
}
if err != nil {
return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err)
}
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
@@ -381,7 +380,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive)
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive)
if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}

View File

@@ -8,26 +8,6 @@ import (
const USER_AGENT_NAME = "k8-operator"
func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncryptedWorkspaceKeyRequest) (GetEncryptedWorkspaceKeyResponse, error) {
endpoint := fmt.Sprintf("%v/v2/workspace/%v/encrypted-key", API_HOST_URL, request.WorkspaceId)
var result GetEncryptedWorkspaceKeyResponse
response, err := httpClient.
R().
SetResult(&result).
SetHeader("User-Agent", USER_AGENT_NAME).
Get(endpoint)
if err != nil {
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response: [response=%s]", response)
}
return result, nil
}
func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDetailsResponse, error) {
var tokenDetailsResponse GetServiceTokenDetailsResponse
response, err := httpClient.
@@ -51,43 +31,6 @@ func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDeta
return tokenDetailsResponse, nil
}
func CallGetSecretsV3(httpClient *resty.Client, request GetEncryptedSecretsV3Request) (GetEncryptedSecretsV3Response, error) {
var secretsResponse GetEncryptedSecretsV3Response
httpRequest := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
SetQueryParam("environment", request.Environment).
SetQueryParam("include_imports", "true"). // TODO needs to be set as a option
SetQueryParam("workspaceId", request.WorkspaceId)
if request.SecretPath != "" {
httpRequest.SetQueryParam("secretPath", request.SecretPath)
}
if request.Recursive {
httpRequest.SetQueryParam("recursive", "true")
}
response, err := httpRequest.Get(fmt.Sprintf("%v/v3/secrets", API_HOST_URL))
if err != nil {
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
responseETag := response.Header().Get("etag")
secretsResponse.Modified = request.ETag != responseETag
secretsResponse.ETag = responseETag
return secretsResponse, nil
}
func CallGetServiceTokenAccountDetailsV2(httpClient *resty.Client) (ServiceAccountDetailsResponse, error) {
var serviceAccountDetailsResponse ServiceAccountDetailsResponse
response, err := httpClient.
@@ -149,42 +92,6 @@ func CallUniversalMachineIdentityRefreshAccessToken(request MachineIdentityUnive
return universalAuthRefreshResponse, nil
}
func CallGetDecryptedSecretsV3(httpClient *resty.Client, request GetDecryptedSecretsV3Request) (GetDecryptedSecretsV3Response, error) {
var decryptedSecretsResponse GetDecryptedSecretsV3Response
req := httpClient.
R().
SetResult(&decryptedSecretsResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
SetQueryParam("include_imports", "true").
SetQueryParam("secretPath", request.SecretPath).
SetQueryParam("workspaceSlug", request.ProjectSlug).
SetQueryParam("environment", request.Environment)
if request.Recursive {
req.SetQueryParam("recursive", "true")
}
if request.ExpandSecretReferences {
req.SetQueryParam("expandSecretReferences", "true")
}
response, err := req.Get(fmt.Sprintf("%v/v3/secrets/raw", API_HOST_URL))
if err != nil {
return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unsuccessful response: [response=%s]", response)
}
responseETag := response.Header().Get("etag")
decryptedSecretsResponse.Modified = request.ETag != responseETag
decryptedSecretsResponse.ETag = responseETag
return decryptedSecretsResponse, nil
}
func CallGetServiceAccountWorkspacePermissionsV2(httpClient *resty.Client) (ServiceAccountWorkspacePermissions, error) {
var serviceAccountWorkspacePermissionsResponse ServiceAccountWorkspacePermissions
response, err := httpClient.

View File

@@ -106,6 +106,7 @@ type GetDecryptedSecretsV3Response struct {
}
type GetDecryptedSecretsV3Request struct {
ProjectID string `json:"workspaceId"`
ProjectSlug string `json:"workspaceSlug"`
Environment string `json:"environment"`
SecretPath string `json:"secretPath"`

View File

@@ -1,10 +1,7 @@
package util
import (
"encoding/base64"
"fmt"
"path"
"regexp"
"strings"
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
@@ -87,7 +84,7 @@ func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalCl
}, nil
}
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string, recursive bool) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClientInterface, fullServiceToken string, etag string, envSlug string, secretPath string, recursive bool) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
@@ -105,51 +102,44 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, en
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
}
encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{
WorkspaceId: serviceTokenDetails.Workspace,
Environment: envSlug,
Recursive: recursive,
ETag: etag,
SecretPath: secretPath,
secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{
ProjectID: serviceTokenDetails.Workspace,
Environment: envSlug,
Recursive: recursive,
SecretPath: secretPath,
IncludeImports: true,
ExpandSecretReferences: true,
})
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, err
}
decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
var environmentVariables []model.SingleEnvironmentVariable
for _, secret := range secrets {
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
Key: secret.SecretKey,
Value: secret.SecretValue,
Type: secret.Type,
ID: secret.ID,
})
}
plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to decrypt the required workspace key")
}
newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables)))
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse.Secrets)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
}
plainTextSecretsMergedWithImports, err := InjectImportedSecret(plainTextWorkspaceKey, plainTextSecrets, encryptedSecretsResponse.ImportedSecrets)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, err
}
// expand secrets that are referenced
expandedSecrets := ExpandSecrets(plainTextSecretsMergedWithImports, fullServiceToken)
return expandedSecrets, model.RequestUpdateUpdateDetails{
Modified: encryptedSecretsResponse.Modified,
ETag: encryptedSecretsResponse.ETag,
return environmentVariables, model.RequestUpdateUpdateDetails{
Modified: etag != newEtag,
ETag: newEtag,
}, nil
}
// Fetches plaintext secrets from an API endpoint using a service account.
// The function fetches the service account details and keys, decrypts the workspace key, fetches the encrypted secrets for the specified project and environment, and decrypts the secrets using the decrypted workspace key.
// Returns the plaintext secrets, encrypted secrets response, and any errors that occurred during the process.
func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalClientInterface, serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
httpClient := resty.New()
httpClient.SetAuthToken(serviceAccountCreds.AccessKey).
SetHeader("Accept", "application/json")
@@ -176,226 +166,36 @@ func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccou
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err)
}
cipherText, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.EncryptedKey)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode EncryptedKey secrets because [err=%v]", err)
}
nonce, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.Nonce)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode nonce secrets because [err=%v]", err)
}
publickey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PublicKey)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PublicKey secrets because [err=%v]", err)
}
privateKey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PrivateKey)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PrivateKey secrets because [err=%v]", err)
}
plainTextWorkspaceKey := crypto.DecryptAsymmetric(cipherText, nonce, publickey, privateKey)
encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{
WorkspaceId: projectId,
Environment: environmentName,
ETag: etag,
secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{
ProjectID: projectId,
Environment: environmentName,
Recursive: false,
SecretPath: "/",
IncludeImports: true,
ExpandSecretReferences: true,
})
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to fetch secrets because [err=%v]", err)
return nil, model.RequestUpdateUpdateDetails{}, err
}
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse.Secrets)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get plain text secrets because [err=%v]", err)
}
var environmentVariables []model.SingleEnvironmentVariable
return plainTextSecrets, model.RequestUpdateUpdateDetails{
Modified: encryptedSecretsResponse.Modified,
ETag: encryptedSecretsResponse.ETag,
}, nil
}
func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) {
cipherx, err := base64.StdEncoding.DecodeString(cipher)
if err != nil {
return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode cipher text [err=%v]", err)
}
keyx, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode key [err=%v]", err)
}
IVx, err := base64.StdEncoding.DecodeString(IV)
if err != nil {
return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode IV [err=%v]", err)
}
tagx, err := base64.StdEncoding.DecodeString(tag)
if err != nil {
return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode tag [err=%v]", err)
}
return DecodedSymmetricEncryptionDetails{
Key: keyx,
Cipher: cipherx,
IV: IVx,
Tag: tagx,
}, nil
}
func GetPlainTextSecrets(key []byte, encryptedSecrets []api.EncryptedSecretV3) ([]model.SingleEnvironmentVariable, error) {
plainTextSecrets := []model.SingleEnvironmentVariable{}
for _, secret := range encryptedSecrets {
// Decrypt key
key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV)
if err != nil {
return nil, fmt.Errorf("unable to decode secret IV for secret key")
}
key_tag, err := base64.StdEncoding.DecodeString(secret.SecretKeyTag)
if err != nil {
return nil, fmt.Errorf("unable to decode secret authentication tag for secret key")
}
key_ciphertext, err := base64.StdEncoding.DecodeString(secret.SecretKeyCiphertext)
if err != nil {
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
}
plainTextKey, err := crypto.DecryptSymmetric(key, key_ciphertext, key_tag, key_iv)
if err != nil {
return nil, fmt.Errorf("unable to symmetrically decrypt secret key")
}
// Decrypt value
value_iv, err := base64.StdEncoding.DecodeString(secret.SecretValueIV)
if err != nil {
return nil, fmt.Errorf("unable to decode secret IV for secret value")
}
value_tag, err := base64.StdEncoding.DecodeString(secret.SecretValueTag)
if err != nil {
return nil, fmt.Errorf("unable to decode secret authentication tag for secret value")
}
value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValueCiphertext)
if err != nil {
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
}
plainTextValue, err := crypto.DecryptSymmetric(key, value_ciphertext, value_tag, value_iv)
if err != nil {
return nil, fmt.Errorf("unable to symmetrically decrypt secret value")
}
plainTextSecret := model.SingleEnvironmentVariable{
Key: string(plainTextKey),
Value: string(plainTextValue),
Type: string(secret.Type),
for _, secret := range secrets {
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
Key: secret.SecretKey,
Value: secret.SecretValue,
Type: secret.Type,
ID: secret.ID,
}
plainTextSecrets = append(plainTextSecrets, plainTextSecret)
})
}
return plainTextSecrets, nil
}
newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables)))
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 {
return ""
// panic(fmt.Errorf("Could not find referred secret with key name %s", key), "Please check it refers a")
}
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 ExpandSecrets(secrets []model.SingleEnvironmentVariable, infisicalToken string) []model.SingleEnvironmentVariable {
expandedSecs := make(map[string]string)
interpolatedSecs := make(map[string]string)
// map[env.secret-path][keyname]Secret
crossEnvRefSecs := make(map[string]map[string]model.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 {
// if not in cross reference cache, fetch it from server
refSecs, _, err := GetPlainTextSecretsViaServiceToken(infisicalToken, "", env, secPath, false)
if err != nil {
fmt.Printf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath)
// 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
return environmentVariables, model.RequestUpdateUpdateDetails{
Modified: etag != newEtag,
ETag: newEtag,
}, nil
}
func getSecretsByKeys(secrets []model.SingleEnvironmentVariable) map[string]model.SingleEnvironmentVariable {
@@ -408,35 +208,6 @@ func getSecretsByKeys(secrets []model.SingleEnvironmentVariable) map[string]mode
return secretMapByName
}
func InjectImportedSecret(plainTextWorkspaceKey []byte, secrets []model.SingleEnvironmentVariable, importedSecrets []api.ImportedSecretV3) ([]model.SingleEnvironmentVariable, error) {
if importedSecrets == nil {
return secrets, nil
}
hasOverriden := make(map[string]bool)
for _, sec := range secrets {
hasOverriden[sec.Key] = true
}
for i := len(importedSecrets) - 1; i >= 0; i-- {
importSec := importedSecrets[i]
plainTextImportedSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, importSec.Secrets)
if err != nil {
return nil, fmt.Errorf("unable to decrypt your imported secrets [err=%v]", err)
}
for _, sec := range plainTextImportedSecrets {
if _, ok := hasOverriden[sec.Key]; !ok {
secrets = append(secrets, sec)
hasOverriden[sec.Key] = true
}
}
}
return secrets, nil
}
func MergeRawImportedSecrets(secrets []model.SingleEnvironmentVariable, importedSecrets []api.ImportedRawSecretV3) []model.SingleEnvironmentVariable {
if importedSecrets == nil {
return secrets