diff --git a/README.md b/README.md
index c63c4f9c1..169d51509 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ Not sure where to get started? [Book a free, non-pressure pairing sessions with
- [GitHub Discussions](https://github.com/Infisical/infisical/discussions) for help with building and discussion.
- [GitHub Issues](https://github.com/Infisical/infisical-cli/issues) for any bugs and errors you encounter using Infisical.
-- [Community Slack](https://join.slack.com/t/infisical/shared_invite/zt-1dgg63ln8-G7PCNJdCymAT9YF3j1ewVA) for hanging out with the community and quick communication with the team.
+- [Community Slack](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g) for hanging out with the community and quick communication with the team.
## Status
diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go
index 1c07f06c8..d9ed47a36 100644
--- a/cli/packages/cmd/run.go
+++ b/cli/packages/cmd/run.go
@@ -33,6 +33,13 @@ var runCmd = &cobra.Command{
return
}
+ substitute, err := cmd.Flags().GetBool("substitute")
+ if err != nil {
+ log.Errorln("Unable to parse the substitute flag")
+ log.Debugln(err)
+ return
+ }
+
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
log.Errorln("Unable to parse the project id flag")
@@ -82,7 +89,13 @@ var runCmd = &cobra.Command{
}
}
- execCmd(args[0], args[1:], envsFromApi)
+ if substitute {
+ substitutions := util.SubstituteSecrets(envsFromApi)
+ execCmd(args[0], args[1:], substitutions)
+ } else {
+ execCmd(args[0], args[1:], envsFromApi)
+ }
+
},
}
@@ -90,6 +103,7 @@ func init() {
rootCmd.AddCommand(runCmd)
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from")
+ runCmd.Flags().Bool("substitute", true, "Parse shell variable substitutions in your secrets")
}
// Credit: inspired by AWS Valut
diff --git a/cli/packages/models/error.go b/cli/packages/models/error.go
new file mode 100644
index 000000000..28e48d54d
--- /dev/null
+++ b/cli/packages/models/error.go
@@ -0,0 +1,14 @@
+package models
+
+import log "github.com/sirupsen/logrus"
+
+// Custom error type so that we can give helpful messages in CLI
+type Error struct {
+ Err error
+ DebugMessage string
+ FriendlyMessage string
+}
+
+func (e *Error) printFriendlyMessage() {
+ log.Infoln(e.FriendlyMessage)
+}
diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go
index 475640713..5cf76d48e 100644
--- a/cli/packages/util/secrets.go
+++ b/cli/packages/util/secrets.go
@@ -4,6 +4,7 @@ import (
"encoding/base64"
"errors"
"fmt"
+ "regexp"
"strings"
"github.com/Infisical/infisical-merge/packages/models"
@@ -205,3 +206,73 @@ func GetWorkSpacesFromAPI(userCreds models.UserCredentials) (workspaces []models
return getWorkSpacesResponse.Workspaces, nil
}
+
+func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string {
+ if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found {
+ return value
+ }
+
+ for _, secret := range secrets {
+ if secret.Key == variableWeAreLookingFor {
+ regex := regexp.MustCompile(`\${([^\}]*)}`)
+ variablesToPopulate := regex.FindAllString(secret.Value, -1)
+
+ // case: variable is a constant so return its value
+ if len(variablesToPopulate) == 0 {
+ return secret.Value
+ }
+
+ valueToEdit := secret.Value
+ for _, variableWithSign := range variablesToPopulate {
+ variableWithoutSign := strings.Trim(variableWithSign, "}")
+ variableWithoutSign = strings.Trim(variableWithoutSign, "${")
+
+ // case: reference to self
+ if variableWithoutSign == secret.Key {
+ hashMapOfSelfRefs[variableWithoutSign] = variableWithoutSign
+ continue
+ } else {
+ var expandedVariableValue string
+
+ if preComputedVariable, found := hashMapOfCompleteVariables[variableWithoutSign]; found {
+ expandedVariableValue = preComputedVariable
+ } else {
+ expandedVariableValue = getExpandedEnvVariable(secrets, variableWithoutSign, hashMapOfCompleteVariables, hashMapOfSelfRefs)
+ hashMapOfCompleteVariables[variableWithoutSign] = expandedVariableValue
+ }
+
+ // If after expanding all the vars above, is the current var a self ref? if so no replacement needed for it
+ if _, found := hashMapOfSelfRefs[variableWithoutSign]; found {
+ continue
+ } else {
+ valueToEdit = strings.ReplaceAll(valueToEdit, variableWithSign, expandedVariableValue)
+ }
+ }
+ }
+
+ return valueToEdit
+
+ } else {
+ continue
+ }
+ }
+
+ return "${" + variableWeAreLookingFor + "}"
+}
+
+func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable {
+ hashMapOfCompleteVariables := make(map[string]string)
+ hashMapOfSelfRefs := make(map[string]string)
+ expandedSecrets := []models.SingleEnvironmentVariable{}
+
+ for _, secret := range secrets {
+ expandedVariable := getExpandedEnvVariable(secrets, secret.Key, hashMapOfCompleteVariables, hashMapOfSelfRefs)
+ expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{
+ Key: secret.Key,
+ Value: expandedVariable,
+ })
+
+ }
+
+ return expandedSecrets
+}
diff --git a/cli/packages/util/secrets_test.go b/cli/packages/util/secrets_test.go
new file mode 100644
index 000000000..513e4f7e3
--- /dev/null
+++ b/cli/packages/util/secrets_test.go
@@ -0,0 +1,160 @@
+package util
+
+import (
+ "testing"
+
+ "github.com/Infisical/infisical-merge/packages/models"
+)
+
+// References to self should return the value unaltered
+func Test_SubstituteSecrets_When_ReferenceToSelf(t *testing.T) {
+
+ var tests = []struct {
+ Key string
+ Value string
+ ExpectedValue string
+ }{
+ {Key: "A", Value: "${A}", ExpectedValue: "${A}"},
+ {Key: "A", Value: "${A} ${A}", ExpectedValue: "${A} ${A}"},
+ {Key: "A", Value: "${A}${A}", ExpectedValue: "${A}${A}"},
+ }
+
+ for _, test := range tests {
+ secret := models.SingleEnvironmentVariable{
+ Key: test.Key,
+ Value: test.Value,
+ }
+
+ secrets := []models.SingleEnvironmentVariable{secret}
+ result := SubstituteSecrets(secrets)
+
+ if result[0].Value != test.ExpectedValue {
+ t.Errorf("Test_SubstituteSecrets_When_ReferenceToSelf: expected %s but got %s for input %s", test.ExpectedValue, result[0].Value, test.Value)
+ }
+
+ }
+}
+
+func Test_SubstituteSecrets_When_ReferenceDoesNotExist(t *testing.T) {
+
+ var tests = []struct {
+ Key string
+ Value string
+ ExpectedValue string
+ }{
+ {Key: "A", Value: "${X}", ExpectedValue: "${X}"},
+ {Key: "A", Value: "${H}HELLO", ExpectedValue: "${H}HELLO"},
+ {Key: "A", Value: "${L}${S}", ExpectedValue: "${L}${S}"},
+ }
+
+ for _, test := range tests {
+ secret := models.SingleEnvironmentVariable{
+ Key: test.Key,
+ Value: test.Value,
+ }
+
+ secrets := []models.SingleEnvironmentVariable{secret}
+ result := SubstituteSecrets(secrets)
+
+ if result[0].Value != test.ExpectedValue {
+ t.Errorf("Test_SubstituteSecrets_When_ReferenceToSelf: expected %s but got %s for input %s", test.ExpectedValue, result[0].Value, test.Value)
+ }
+
+ }
+}
+
+func Test_SubstituteSecrets_When_ReferenceDoesNotExist_And_Self_Referencing(t *testing.T) {
+
+ tests := []struct {
+ Key string
+ Value string
+ ExpectedValue string
+ }{
+ {
+ Key: "O",
+ Value: "${P} ==$$ ${X} ${UNKNOWN} ${A}",
+ ExpectedValue: "DOMAIN === ${A} DOMAIN >>> ==$$ DOMAIN ${UNKNOWN} ${A}",
+ },
+ {
+ Key: "X",
+ Value: "DOMAIN",
+ ExpectedValue: "DOMAIN",
+ },
+ {
+ Key: "A",
+ Value: "*${A}* ${X}",
+ ExpectedValue: "*${A}* DOMAIN",
+ },
+ {
+ Key: "H",
+ Value: "${X} >>>",
+ ExpectedValue: "DOMAIN >>>",
+ },
+ {
+ Key: "P",
+ Value: "DOMAIN === ${A} ${H}",
+ ExpectedValue: "DOMAIN === ${A} DOMAIN >>>",
+ },
+ {
+ Key: "T",
+ Value: "${P} ==$$ ${X} ${UNKNOWN} ${A} ${P} ==$$ ${X} ${UNKNOWN} ${A}",
+ ExpectedValue: "DOMAIN === ${A} DOMAIN >>> ==$$ DOMAIN ${UNKNOWN} ${A} DOMAIN === ${A} DOMAIN >>> ==$$ DOMAIN ${UNKNOWN} ${A}",
+ },
+ {
+ Key: "S",
+ Value: "${ SSS$$ ${HEY}",
+ ExpectedValue: "${ SSS$$ ${HEY}",
+ },
+ }
+
+ secrets := []models.SingleEnvironmentVariable{}
+ for _, test := range tests {
+ secrets = append(secrets, models.SingleEnvironmentVariable{Key: test.Key, Value: test.Value})
+ }
+
+ results := SubstituteSecrets(secrets)
+
+ for index, expanded := range results {
+ if expanded.Value != tests[index].ExpectedValue {
+ t.Errorf("Test_SubstituteSecrets_When_ReferenceToSelf: expected [%s] but got [%s] for input [%s]", tests[index].ExpectedValue, expanded.Value, tests[index].Value)
+ }
+ }
+}
+
+func Test_SubstituteSecrets_When_No_SubstituteNeeded(t *testing.T) {
+
+ tests := []struct {
+ Key string
+ Value string
+ ExpectedValue string
+ }{
+ {
+ Key: "DOMAIN",
+ Value: "infisical.com",
+ ExpectedValue: "infisical.com",
+ },
+ {
+ Key: "API_KEY",
+ Value: "hdgsvjshcgkdckhevdkd",
+ ExpectedValue: "hdgsvjshcgkdckhevdkd",
+ },
+ {
+ Key: "ENV",
+ Value: "PROD",
+ ExpectedValue: "PROD",
+ },
+ }
+
+ secrets := []models.SingleEnvironmentVariable{}
+ for _, test := range tests {
+ secrets = append(secrets, models.SingleEnvironmentVariable{Key: test.Key, Value: test.Value})
+ }
+
+ results := SubstituteSecrets(secrets)
+
+ for index, expanded := range results {
+ if expanded.Value != tests[index].ExpectedValue {
+ t.Errorf("Test_SubstituteSecrets_When_ReferenceToSelf: expected [%s] but got [%s] for input [%s]", tests[index].ExpectedValue, expanded.Value, tests[index].Value)
+ }
+ }
+}
diff --git a/frontend/components/basic/dialog/AddServiceTokenDialog.js b/frontend/components/basic/dialog/AddServiceTokenDialog.js
index 0e084d29d..8f5419502 100644
--- a/frontend/components/basic/dialog/AddServiceTokenDialog.js
+++ b/frontend/components/basic/dialog/AddServiceTokenDialog.js
@@ -8,7 +8,7 @@ import nacl from "tweetnacl";
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
-import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/crypto";
+import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
import InputField from "../InputField";
import ListBox from "../Listbox";
diff --git a/frontend/components/basic/layout.js b/frontend/components/basic/layout.js
index 382eb2777..edac0dc9b 100644
--- a/frontend/components/basic/layout.js
+++ b/frontend/components/basic/layout.js
@@ -20,7 +20,7 @@ import createWorkspace from "~/pages/api/workspace/createWorkspace";
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import NavBarDashboard from "../navigation/NavBarDashboard";
-import { decryptAssymmetric, encryptAssymmetric } from "../utilities/crypto";
+import { decryptAssymmetric, encryptAssymmetric } from "../utilities/cryptography/crypto";
import Button from "./buttons/Button";
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
import Listbox from "./Listbox";
diff --git a/frontend/components/basic/table/UserTable.js b/frontend/components/basic/table/UserTable.js
index a09ff6e16..4d364b4a5 100644
--- a/frontend/components/basic/table/UserTable.js
+++ b/frontend/components/basic/table/UserTable.js
@@ -15,7 +15,7 @@ import Listbox from "../Listbox";
const {
decryptAssymmetric,
encryptAssymmetric,
-} = require("../../utilities/crypto");
+} = require("../../utilities/cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
diff --git a/frontend/components/billing/Card.js b/frontend/components/billing/Card.js
deleted file mode 100644
index 07f91b07a..000000000
--- a/frontend/components/billing/Card.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from "react";
-import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
-import { faCheck, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
-import { faCircle } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-
-export default function Card({ card, changeSelectedCard, selected }) {
- function creditCardBrandIcon(cc) {
- if (cc == "visa") {
- return faCcVisa;
- } else if ((cc = "mastercard")) {
- return faCcMastercard;
- } else return faQuestionCircle;
- }
-
- return (
-
- );
-}
diff --git a/frontend/components/navigation/NavBarDashboard.js b/frontend/components/navigation/NavBarDashboard.js
index 154bf0a55..d4f0c704a 100644
--- a/frontend/components/navigation/NavBarDashboard.js
+++ b/frontend/components/navigation/NavBarDashboard.js
@@ -27,8 +27,8 @@ import guidGenerator from "../utilities/randomId";
const supportOptions = [
[
diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js index 35a9a6f06..9ae9c1c7c 100644 --- a/frontend/pages/integrations/[id].js +++ b/frontend/pages/integrations/[id].js @@ -13,8 +13,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Button from "~/components/basic/buttons/Button"; import ListBox from "~/components/basic/Listbox"; import NavHeader from "~/components/navigation/NavHeader"; -import getSecretsForProject from "~/utilities/getSecretsForProject"; -import pushKeysIntegration from "~/utilities/pushKeysIntegration"; +import getSecretsForProject from "~/components/utilities/secrets/getSecretsForProject"; +import pushKeysIntegration from "~/components/utilities/secrets/pushKeysIntegration"; import guidGenerator from "~/utilities/randomId"; import deleteIntegration from "../api/integrations/DeleteIntegration"; diff --git a/frontend/pages/settings/personal/[id].js b/frontend/pages/settings/personal/[id].js index 2b5b90a35..9c4ad59bd 100644 --- a/frontend/pages/settings/personal/[id].js +++ b/frontend/pages/settings/personal/[id].js @@ -6,9 +6,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Button from "~/components/basic/buttons/Button"; import InputField from "~/components/basic/InputField"; import NavHeader from "~/components/navigation/NavHeader"; -import changePassword from "~/utilities/changePassword"; +import changePassword from "~/components/utilities/cryptography/changePassword"; import passwordCheck from "~/utilities/checks/PasswordCheck"; -import issueBackupKey from "~/utilities/issueBackupKey"; +import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import getUser from "../../api/user/getUser"; diff --git a/frontend/pages/signup.js b/frontend/pages/signup.js index 3998bcf42..e9f000ed2 100644 --- a/frontend/pages/signup.js +++ b/frontend/pages/signup.js @@ -7,13 +7,13 @@ import { useRouter } from "next/router"; import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Aes256Gcm from "~/components/aes-256-gcm"; +import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; import Button from "~/components/basic/buttons/Button"; import Error from "~/components/basic/Error"; import InputField from "~/components/basic/InputField"; import attemptLogin from "~/utilities/attemptLogin"; import passwordCheck from "~/utilities/checks/PasswordCheck"; -import issueBackupKey from "~/utilities/issueBackupKey"; +import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import checkEmailVerificationCode from "./api/auth/CheckEmailVerificationCode"; import completeAccountInformationSignup from "./api/auth/CompleteAccountInformationSignup"; diff --git a/frontend/pages/signupinvite.js b/frontend/pages/signupinvite.js index 0f6a5c1c4..256bbb83f 100644 --- a/frontend/pages/signupinvite.js +++ b/frontend/pages/signupinvite.js @@ -6,12 +6,12 @@ import { useRouter } from "next/router"; import { faCheck, faWarning,faX } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Aes256Gcm from "~/components/aes-256-gcm"; +import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; import Button from "~/components/basic/buttons/Button"; import InputField from "~/components/basic/InputField"; import attemptLogin from "~/utilities/attemptLogin"; import passwordCheck from "~/utilities/checks/PasswordCheck"; -import issueBackupKey from "~/utilities/issueBackupKey"; +import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite"; import verifySignupInvite from "./api/auth/VerifySignupInvite"; diff --git a/frontend/pages/users/[id].js b/frontend/pages/users/[id].js index 0d1fd7d3d..f46abdf1d 100644 --- a/frontend/pages/users/[id].js +++ b/frontend/pages/users/[id].js @@ -23,7 +23,7 @@ const crypto = require("crypto"); const { decryptAssymmetric, encryptAssymmetric, -} = require("../../components/utilities/crypto"); +} = require("../../components/utilities/cryptography/crypto"); const nacl = require("tweetnacl"); nacl.util = require("tweetnacl-util");