mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Add frontend, backend and CLI
This commit is contained in:
130
cli/packages/cmd/init.go
Normal file
130
cli/packages/cmd/init.go
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/manifoldco/promptui"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// runCmd represents the run command
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Used to initialize your project with Infisical",
|
||||
DisableFlagsInUseLine: true,
|
||||
Example: "infisical init",
|
||||
Args: cobra.ExactArgs(0),
|
||||
PreRun: toggleDebug,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// check if user is logged
|
||||
hasUserLoggedInbefore, loggedInUserEmail, err := util.IsUserLoggedIn()
|
||||
if err != nil {
|
||||
log.Info("Unexpected issue occurred while checking login status. To see more details, add flag --debug")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasUserLoggedInbefore {
|
||||
log.Infoln("No logged in user. To login, please run command [infisical login]")
|
||||
return
|
||||
}
|
||||
|
||||
if util.WorkspaceConfigFileExists() {
|
||||
shouldOverride, err := shouldOverrideWorkspacePrompt()
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse your answer")
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !shouldOverride {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
userCreds, err := util.GetUserCredsFromKeyRing(loggedInUserEmail)
|
||||
if err != nil {
|
||||
log.Infoln("Unable to get user creds from key ring")
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
workspaces, err := util.GetWorkSpacesFromAPI(userCreds)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to pull your projects. To see more logs add the --debug flag to this command")
|
||||
log.Debugln("Unable to get your projects because:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(workspaces) == 0 {
|
||||
log.Infoln("You don't have any projects created in Infisical. You must first create a project at https://infisical.com")
|
||||
return
|
||||
}
|
||||
|
||||
var workspaceNames []string
|
||||
for _, workspace := range workspaces {
|
||||
workspaceNames = append(workspaceNames, workspace.Name)
|
||||
}
|
||||
|
||||
prompt := promptui.Select{
|
||||
Label: "Which of your Infisical projects would you like to connect this project to?",
|
||||
Items: workspaceNames,
|
||||
Size: 7,
|
||||
}
|
||||
|
||||
index, _, err := prompt.Run()
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse your response")
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = writeWorkspaceFile(workspaces[index])
|
||||
if err != nil {
|
||||
log.Errorln("Something went wrong when creating your workspace file")
|
||||
log.Debug("Error while writing your workspace file:", err)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(initCmd)
|
||||
}
|
||||
|
||||
func writeWorkspaceFile(selectedWorkspace models.Workspace) error {
|
||||
workspaceFileToSave := models.WorkspaceConfigFile{
|
||||
WorkspaceId: selectedWorkspace.ID,
|
||||
}
|
||||
|
||||
marshalledWorkspaceFile, err := json.Marshal(workspaceFileToSave)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = util.WriteToFile(util.INFISICAL_WORKSPACE_CONFIG_FILE_NAME, marshalledWorkspaceFile, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldOverrideWorkspacePrompt() (bool, error) {
|
||||
prompt := promptui.Select{
|
||||
Label: "A workspace config file already exists here. Would you like to override? Select[Yes/No]",
|
||||
Items: []string{"No", "Yes"},
|
||||
}
|
||||
_, result, err := prompt.Run()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return result == "Yes", nil
|
||||
}
|
||||
27
cli/packages/cmd/logging.go
Normal file
27
cli/packages/cmd/logging.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var debugLogging bool
|
||||
|
||||
type PlainFormatter struct {
|
||||
}
|
||||
|
||||
func (f *PlainFormatter) Format(entry *log.Entry) ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%s\n", entry.Message)), nil
|
||||
}
|
||||
func toggleDebug(cmd *cobra.Command, args []string) {
|
||||
if debugLogging {
|
||||
log.Info("Debug logs enabled")
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
} else {
|
||||
plainFormatter := new(PlainFormatter)
|
||||
log.SetFormatter(plainFormatter)
|
||||
}
|
||||
}
|
||||
242
cli/packages/cmd/login.go
Normal file
242
cli/packages/cmd/login.go
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/srp"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/manifoldco/promptui"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// loginCmd represents the login command
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Login into your Infisical account",
|
||||
DisableFlagsInUseLine: true,
|
||||
PreRun: toggleDebug,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
hasUserLoggedInbefore, currentLoggedInUserEmail, err := util.IsUserLoggedIn()
|
||||
if err != nil {
|
||||
log.Debugln(err)
|
||||
}
|
||||
|
||||
if hasUserLoggedInbefore {
|
||||
shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserEmail)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse your answer")
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !shouldOverride {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorln("Unable to get current logged in user.")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
email, password, err := askForLoginCredentials()
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse email and password for authentication")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
userCredentials, err := getFreshUserCredentials(email, password)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to authenticate with the provided credentials, please try again")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(userCredentials.EncryptedPrivateKey)
|
||||
tag, err := base64.StdEncoding.DecodeString(userCredentials.Tag)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to decode the auth tag")
|
||||
log.Debugln(err)
|
||||
}
|
||||
|
||||
IV, err := base64.StdEncoding.DecodeString(userCredentials.IV)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to decode the IV/Nonce")
|
||||
log.Debugln(err)
|
||||
}
|
||||
|
||||
paddedPassword := fmt.Sprintf("%032s", password)
|
||||
key := []byte(paddedPassword)
|
||||
|
||||
decryptedPrivateKey, err := util.DecryptSymmetric(key, encryptedPrivateKey, tag, IV)
|
||||
if err != nil || len(decryptedPrivateKey) == 0 {
|
||||
log.Errorln("There was an issue decrypting your keys")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
userCredentialsToBeStored := &models.UserCredentials{
|
||||
Email: email,
|
||||
PrivateKey: string(decryptedPrivateKey),
|
||||
JTWToken: userCredentials.JTWToken,
|
||||
}
|
||||
|
||||
err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to store your credentials in system key ring")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = util.WriteInitalConfig(userCredentialsToBeStored)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to write write to Infisical Config file. Please try again")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infoln("Nice! You are loggin as:", email)
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(loginCmd)
|
||||
}
|
||||
|
||||
func askForLoginCredentials() (email string, password string, err error) {
|
||||
validateEmail := func(input string) error {
|
||||
result, err := regexp.MatchString("^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$", input)
|
||||
if err != nil || !result {
|
||||
return errors.New("this doesn't look like an email address")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
emailPrompt := promptui.Prompt{
|
||||
Label: "Email",
|
||||
Validate: validateEmail,
|
||||
}
|
||||
|
||||
userEmail, err := emailPrompt.Run()
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
validatePassword := func(input string) error {
|
||||
if len(input) < 1 {
|
||||
return errors.New("please enter a valid password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
passwordPrompt := promptui.Prompt{
|
||||
Label: "Password",
|
||||
Validate: validatePassword,
|
||||
Mask: '*',
|
||||
}
|
||||
|
||||
userPassword, err := passwordPrompt.Run()
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return userEmail, userPassword, nil
|
||||
}
|
||||
|
||||
func getFreshUserCredentials(email string, password string) (*models.LoginTwoResponse, error) {
|
||||
httpClient := resty.New()
|
||||
httpClient.SetRetryCount(5)
|
||||
|
||||
params := srp.GetParams(4096)
|
||||
secret1 := srp.GenKey()
|
||||
srpClient := srp.NewClient(params, []byte(email), []byte(password), secret1)
|
||||
srpA := hex.EncodeToString(srpClient.ComputeA())
|
||||
|
||||
// ** Login one
|
||||
loginOneRequest := models.LoginOneRequest{
|
||||
Email: email,
|
||||
ClientPublicKey: srpA,
|
||||
}
|
||||
|
||||
var loginOneResponseResult models.LoginOneResponse
|
||||
|
||||
loginOneResponse, err := httpClient.
|
||||
R().
|
||||
SetBody(loginOneRequest).
|
||||
SetResult(&loginOneResponseResult).
|
||||
Post(fmt.Sprintf("%v/%v", util.INFISICAL_URL, "login1"))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if loginOneResponse.StatusCode() > 299 {
|
||||
return nil, fmt.Errorf("ops, unsuccessful response code. [response=%v]", loginOneResponse)
|
||||
}
|
||||
|
||||
// **** Login 2
|
||||
serverPublicKey_bytearray, err := hex.DecodeString(loginOneResponseResult.ServerPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userSalt, err := hex.DecodeString(loginOneResponseResult.ServerSalt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srpClient.SetSalt(userSalt, []byte(email), []byte(password))
|
||||
srpClient.SetB(serverPublicKey_bytearray)
|
||||
|
||||
srpM1 := srpClient.ComputeM1()
|
||||
|
||||
LoginTwoRequest := models.LoginTwoRequest{
|
||||
Email: email,
|
||||
ClientProof: hex.EncodeToString(srpM1),
|
||||
}
|
||||
|
||||
var loginTwoResponseResult models.LoginTwoResponse
|
||||
loginTwoResponse, err := httpClient.
|
||||
R().
|
||||
SetBody(LoginTwoRequest).
|
||||
SetResult(&loginTwoResponseResult).
|
||||
Post(fmt.Sprintf("%v/%v", util.INFISICAL_URL, "login2"))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if loginTwoResponse.StatusCode() > 299 {
|
||||
return nil, fmt.Errorf("ops, unsuccessful response code. [response=%v]", loginTwoResponse)
|
||||
}
|
||||
|
||||
return &loginTwoResponseResult, nil
|
||||
}
|
||||
|
||||
func shouldOverrideLoginPrompt(currentLoggedInUserEmail string) (bool, error) {
|
||||
prompt := promptui.Select{
|
||||
Label: fmt.Sprintf("There seems to be a user already logged in with the email: %s. Would you like to override that login? Select[Yes/No]", currentLoggedInUserEmail),
|
||||
Items: []string{"No", "Yes"},
|
||||
}
|
||||
_, result, err := prompt.Run()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return result == "Yes", err
|
||||
}
|
||||
34
cli/packages/cmd/root.go
Normal file
34
cli/packages/cmd/root.go
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "infisical",
|
||||
Short: "Infisical CLI is used to inject environment variables into any process",
|
||||
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
|
||||
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
|
||||
Version: "1.0.0",
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging")
|
||||
rootCmd.PersistentFlags().StringVar(&util.INFISICAL_URL, "domain", "https://api.infisical.com", "Point the CLI to your own backend")
|
||||
}
|
||||
147
cli/packages/cmd/run.go
Normal file
147
cli/packages/cmd/run.go
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// runCmd represents the run command
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run [any infisical run command flags] -- [your application start command]",
|
||||
Short: "Used to inject environments variables into your application process",
|
||||
DisableFlagsInUseLine: true,
|
||||
Example: "infisical run --stage=prod -- npm run dev",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRun: toggleDebug,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
stageName, err := cmd.Flags().GetString("stage")
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse the stage flag")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
projectId, err := cmd.Flags().GetString("projectId")
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse the project id flag")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
var envsFromApi []models.SingleEnvironmentVariable
|
||||
infisicalToken := os.Getenv(util.INFISICAL_SERVICE_TOKEN)
|
||||
if infisicalToken == "" {
|
||||
hasUserLoggedInbefore, loggedInUserEmail, err := util.IsUserLoggedIn()
|
||||
if err != nil {
|
||||
log.Info("Unexpected issue occurred while checking login status. To see more details, add flag --debug")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasUserLoggedInbefore {
|
||||
log.Infoln("No logged in user. To login, please run command [infisical login]")
|
||||
return
|
||||
}
|
||||
|
||||
userCreds, err := util.GetUserCredsFromKeyRing(loggedInUserEmail)
|
||||
if err != nil {
|
||||
log.Infoln("Unable to get user creds from key ring")
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !util.WorkspaceConfigFileExists() {
|
||||
log.Infoln("Your project is not connected to a project yet. Run command [infisical init]")
|
||||
return
|
||||
}
|
||||
|
||||
envsFromApi, err = util.GetSecretsFromAPIUsingCurrentLoggedInUser(stageName, userCreds)
|
||||
if err != nil {
|
||||
log.Errorln("Something went wrong when pulling secrets using your logged in credentials. If the issue persists, double check your project id/try logging in again.")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
envsFromApi, err = util.GetSecretsFromAPIUsingInfisicalToken(infisicalToken, stageName, projectId)
|
||||
if err != nil {
|
||||
log.Errorln("Something went wrong when pulling secrets using your Infisical token. Double check the token, project id or environment name (dev, prod, ect.)")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
execCmd(args[0], args[1:], envsFromApi)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(runCmd)
|
||||
runCmd.Flags().StringP("stage", "s", "dev", "Set the stage (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")
|
||||
}
|
||||
|
||||
// Credit: inspired by AWS Valut
|
||||
func execCmd(command string, args []string, envs []models.SingleEnvironmentVariable) error {
|
||||
log.Debugln("Secrets to inject:", envs)
|
||||
log.Debugf("executing command: %s %s \n", command, strings.Join(args, " "))
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = getAllEnvs(envs)
|
||||
|
||||
sigChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChannel)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
sig := <-sigChannel
|
||||
_ = cmd.Process.Signal(sig) // process all sigs
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
_ = cmd.Process.Signal(os.Kill)
|
||||
return fmt.Errorf("Failed to wait for command termination: %v", err)
|
||||
}
|
||||
|
||||
waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus)
|
||||
os.Exit(waitStatus.ExitStatus())
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAllEnvs(envsToInject []models.SingleEnvironmentVariable) []string {
|
||||
env_map := make(map[string]string)
|
||||
|
||||
for _, env := range os.Environ() {
|
||||
splitEnv := strings.Split(env, "=")
|
||||
env_map[splitEnv[0]] = splitEnv[1]
|
||||
}
|
||||
|
||||
for _, env := range envsToInject {
|
||||
env_map[env.Key] = env.Value // overrite any envs with ones to inject if they clash
|
||||
}
|
||||
|
||||
var allEnvs []string
|
||||
for key, value := range env_map {
|
||||
allEnvs = append(allEnvs, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
|
||||
return allEnvs
|
||||
}
|
||||
Reference in New Issue
Block a user