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
|
||||
}
|
||||
130
cli/packages/models/api.go
Normal file
130
cli/packages/models/api.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// Stores info for login one
|
||||
type LoginOneRequest struct {
|
||||
Email string `json:"email"`
|
||||
ClientPublicKey string `json:"clientPublicKey"`
|
||||
}
|
||||
|
||||
type LoginOneResponse struct {
|
||||
ServerPublicKey string `json:"serverPublicKey"`
|
||||
ServerSalt string `json:"salt"`
|
||||
}
|
||||
|
||||
// Stores info for login two
|
||||
|
||||
type LoginTwoRequest struct {
|
||||
Email string `json:"email"`
|
||||
ClientProof string `json:"clientProof"`
|
||||
}
|
||||
|
||||
type LoginTwoResponse struct {
|
||||
JTWToken string `json:"token"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
EncryptedPrivateKey string `json:"encryptedPrivateKey"`
|
||||
IV string `json:"iv"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
type PullSecretsRequest struct {
|
||||
Environment string `json:"environment"`
|
||||
}
|
||||
|
||||
type PullSecretsResponse struct {
|
||||
Secrets []struct {
|
||||
ID string `json:"_id"`
|
||||
Workspace string `json:"workspace"`
|
||||
Type string `json:"type"`
|
||||
Environment string `json:"environment"`
|
||||
SecretKeyCiphertext string `json:"secretKeyCiphertext"`
|
||||
SecretKeyIV string `json:"secretKeyIV"`
|
||||
SecretKeyTag string `json:"secretKeyTag"`
|
||||
SecretKeyHash string `json:"secretKeyHash"`
|
||||
SecretValueCiphertext string `json:"secretValueCiphertext"`
|
||||
SecretValueIV string `json:"secretValueIV"`
|
||||
SecretValueTag string `json:"secretValueTag"`
|
||||
SecretValueHash string `json:"secretValueHash"`
|
||||
V int `json:"__v"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
User string `json:"user,omitempty"`
|
||||
} `json:"secrets"`
|
||||
Key struct {
|
||||
ID string `json:"_id"`
|
||||
EncryptedKey string `json:"encryptedKey"`
|
||||
Nonce string `json:"nonce"`
|
||||
Sender struct {
|
||||
ID string `json:"_id"`
|
||||
Email string `json:"email"`
|
||||
CustomerID string `json:"customerId"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
V int `json:"__v"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
} `json:"sender"`
|
||||
Receiver string `json:"receiver"`
|
||||
Workspace string `json:"workspace"`
|
||||
V int `json:"__v"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
} `json:"key"`
|
||||
}
|
||||
|
||||
type PullSecretsByInfisicalTokenResponse struct {
|
||||
Secrets []struct {
|
||||
ID string `json:"_id"`
|
||||
Workspace string `json:"workspace"`
|
||||
Type string `json:"type"`
|
||||
Environment string `json:"environment"`
|
||||
SecretKey struct {
|
||||
Workspace string `json:"workspace"`
|
||||
Ciphertext string `json:"ciphertext"`
|
||||
Iv string `json:"iv"`
|
||||
Tag string `json:"tag"`
|
||||
Hash string `json:"hash"`
|
||||
} `json:"secretKey"`
|
||||
SecretValue struct {
|
||||
Workspace string `json:"workspace"`
|
||||
Ciphertext string `json:"ciphertext"`
|
||||
Iv string `json:"iv"`
|
||||
Tag string `json:"tag"`
|
||||
Hash string `json:"hash"`
|
||||
} `json:"secretValue"`
|
||||
} `json:"secrets"`
|
||||
Key struct {
|
||||
EncryptedKey string `json:"encryptedKey"`
|
||||
Nonce string `json:"nonce"`
|
||||
Sender struct {
|
||||
PublicKey string `json:"publicKey"`
|
||||
} `json:"sender"`
|
||||
Receiver struct {
|
||||
RefreshVersion int `json:"refreshVersion"`
|
||||
ID string `json:"_id"`
|
||||
Email string `json:"email"`
|
||||
CustomerID string `json:"customerId"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
V int `json:"__v"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
} `json:"receiver"`
|
||||
Workspace string `json:"workspace"`
|
||||
} `json:"key"`
|
||||
}
|
||||
|
||||
type GetWorkSpacesResponse struct {
|
||||
Workspaces []Workspace `json:"workspaces"`
|
||||
}
|
||||
type Workspace struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
Plan string `json:"plan,omitempty"`
|
||||
V int `json:"__v"`
|
||||
Organization string `json:"organization,omitempty"`
|
||||
}
|
||||
21
cli/packages/models/cli.go
Normal file
21
cli/packages/models/cli.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
type UserCredentials struct {
|
||||
Email string `json:"email"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
JTWToken string `json:"JTWToken"`
|
||||
}
|
||||
|
||||
// The file struct for Infisical config file
|
||||
type ConfigFile struct {
|
||||
LoggedInUserEmail string `json:"loggedInUserEmail"`
|
||||
}
|
||||
|
||||
type SingleEnvironmentVariable struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type WorkspaceConfigFile struct {
|
||||
WorkspaceId string `json:"workspaceId"`
|
||||
}
|
||||
140
cli/packages/srp/client.go
Normal file
140
cli/packages/srp/client.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type SRPClient struct {
|
||||
Params *SRPParams
|
||||
Secret1 *big.Int
|
||||
Multiplier *big.Int
|
||||
A *big.Int
|
||||
X *big.Int
|
||||
M1 []byte
|
||||
M2 []byte
|
||||
K []byte
|
||||
u *big.Int
|
||||
s *big.Int
|
||||
}
|
||||
|
||||
func NewClient(params *SRPParams, identity, password, secret1 []byte) *SRPClient {
|
||||
multiplier := getMultiplier(params)
|
||||
secret1Int := intFromBytes(secret1)
|
||||
Ab := getA(params, secret1Int)
|
||||
A := intFromBytes(Ab)
|
||||
x := getx(params, []byte(""), identity, password) // salt has to be set using SetSalt
|
||||
|
||||
return &SRPClient{
|
||||
Params: params,
|
||||
Multiplier: multiplier,
|
||||
Secret1: secret1Int,
|
||||
A: A,
|
||||
X: x,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SRPClient) ComputeA() []byte {
|
||||
return intToBytes(c.A)
|
||||
}
|
||||
|
||||
// ComputeVerifier returns a verifier that is calculated as described in
|
||||
// Section 3 of [SRP-RFC]
|
||||
func ComputeVerifier(params *SRPParams, salt, identity, password []byte) []byte {
|
||||
x := getx(params, salt, identity, password)
|
||||
vNum := new(big.Int)
|
||||
vNum.Exp(params.G, x, params.N)
|
||||
|
||||
return padToN(vNum, params)
|
||||
}
|
||||
|
||||
func (c *SRPClient) SetB(Bb []byte) {
|
||||
B := intFromBytes(Bb)
|
||||
u := getu(c.Params, c.A, B)
|
||||
S := clientGetS(c.Params, c.Multiplier, c.X, c.Secret1, B, u)
|
||||
|
||||
c.K = getK(c.Params, S)
|
||||
c.M1 = getM1(c.Params, intToBytes(c.A), Bb, c.K) // modified S -> c.K
|
||||
c.M2 = getM2(c.Params, intToBytes(c.A), c.M1, c.K)
|
||||
|
||||
c.u = u // Only for tests
|
||||
c.s = intFromBytes(S) // Only for tests
|
||||
}
|
||||
|
||||
func (c *SRPClient) SetSalt(salt, identity, password []byte) {
|
||||
c.X = getx(c.Params, salt, identity, password) //Overwrite
|
||||
}
|
||||
|
||||
func (c *SRPClient) ComputeM1() []byte {
|
||||
if c.M1 == nil {
|
||||
panic("Incomplete protocol")
|
||||
}
|
||||
|
||||
return c.M1
|
||||
}
|
||||
|
||||
func (c *SRPClient) ComputeK() []byte {
|
||||
return c.K
|
||||
}
|
||||
|
||||
func (c *SRPClient) CheckM2(M2 []byte) error {
|
||||
if !bytes.Equal(c.M2, M2) {
|
||||
return errors.New("M2 didn't check")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getA(params *SRPParams, a *big.Int) []byte {
|
||||
ANum := new(big.Int)
|
||||
ANum.Exp(params.G, a, params.N)
|
||||
return padToN(ANum, params)
|
||||
}
|
||||
|
||||
func clientGetS(params *SRPParams, k, x, a, B, u *big.Int) []byte {
|
||||
BLessThan0 := B.Cmp(big.NewInt(0)) <= 0
|
||||
NLessThanB := params.N.Cmp(B) <= 0
|
||||
if BLessThan0 || NLessThanB {
|
||||
panic("invalid server-supplied 'B', must be 1..N-1")
|
||||
}
|
||||
|
||||
result1 := new(big.Int)
|
||||
result1.Exp(params.G, x, params.N)
|
||||
|
||||
result2 := new(big.Int)
|
||||
result2.Mul(k, result1)
|
||||
|
||||
result3 := new(big.Int)
|
||||
result3.Sub(B, result2)
|
||||
|
||||
result4 := new(big.Int)
|
||||
result4.Mul(u, x)
|
||||
|
||||
result5 := new(big.Int)
|
||||
result5.Add(a, result4)
|
||||
|
||||
result6 := new(big.Int)
|
||||
result6.Exp(result3, result5, params.N)
|
||||
|
||||
result7 := new(big.Int)
|
||||
result7.Mod(result6, params.N)
|
||||
|
||||
return padToN(result7, params)
|
||||
}
|
||||
|
||||
func getx(params *SRPParams, salt, I, P []byte) *big.Int {
|
||||
var ipBytes []byte
|
||||
ipBytes = append(ipBytes, I...)
|
||||
ipBytes = append(ipBytes, []byte(":")...)
|
||||
ipBytes = append(ipBytes, P...)
|
||||
|
||||
hashIP := params.Hash.New()
|
||||
hashIP.Write(ipBytes)
|
||||
|
||||
hashX := params.Hash.New()
|
||||
hashX.Write(salt)
|
||||
hashX.Write(hashToBytes(hashIP))
|
||||
|
||||
return hashToInt(hashX)
|
||||
}
|
||||
95
cli/packages/srp/params.go
Normal file
95
cli/packages/srp/params.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Map of bits to <g, N> tuple
|
||||
type SRPParams struct {
|
||||
G *big.Int
|
||||
N *big.Int
|
||||
Hash crypto.Hash
|
||||
NLengthBits int
|
||||
}
|
||||
|
||||
var knownGroups map[int]*SRPParams
|
||||
|
||||
func createParams(G int64, nBitLength int, hash crypto.Hash, NHex string) *SRPParams {
|
||||
p := SRPParams{
|
||||
G: big.NewInt(G),
|
||||
N: new(big.Int),
|
||||
NLengthBits: nBitLength,
|
||||
Hash: hash,
|
||||
}
|
||||
|
||||
b := bytesFromHexString(NHex)
|
||||
p.N.SetBytes(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
func GetParams(G int) *SRPParams {
|
||||
params := knownGroups[G]
|
||||
if params == nil {
|
||||
panic(fmt.Sprintf("Params don't exist for %v", G))
|
||||
} else {
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
knownGroups = make(map[int]*SRPParams)
|
||||
|
||||
knownGroups[1024] = createParams(2, 1024, crypto.SHA1, `
|
||||
EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B 9EA2314C
|
||||
9C256576 D674DF74 96EA81D3 383B4813 D692C6E0 E0D5D8E2 50B98BE4
|
||||
8E495C1D 6089DAD1 5DC7D7B4 6154D6B6 CE8EF4AD 69B15D49 82559B29
|
||||
7BCF1885 C529F566 660E57EC 68EDBC3C 05726CC0 2FD4CBF4 976EAA9A
|
||||
FD5138FE 8376435B 9FC61D2F C0EB06E3`)
|
||||
|
||||
knownGroups[1536] = createParams(2, 1536, crypto.SHA1, `
|
||||
9DEF3CAF B939277A B1F12A86 17A47BBB DBA51DF4 99AC4C80 BEEEA961
|
||||
4B19CC4D 5F4F5F55 6E27CBDE 51C6A94B E4607A29 1558903B A0D0F843
|
||||
80B655BB 9A22E8DC DF028A7C EC67F0D0 8134B1C8 B9798914 9B609E0B
|
||||
E3BAB63D 47548381 DBC5B1FC 764E3F4B 53DD9DA1 158BFD3E 2B9C8CF5
|
||||
6EDF0195 39349627 DB2FD53D 24B7C486 65772E43 7D6C7F8C E442734A
|
||||
F7CCB7AE 837C264A E3A9BEB8 7F8A2FE9 B8B5292E 5A021FFF 5E91479E
|
||||
8CE7A28C 2442C6F3 15180F93 499A234D CF76E3FE D135F9BB
|
||||
`)
|
||||
|
||||
knownGroups[2048] = createParams(2, 2048, crypto.SHA256, `
|
||||
AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294
|
||||
3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D
|
||||
CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB
|
||||
D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74
|
||||
7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A
|
||||
436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D
|
||||
5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73
|
||||
03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6
|
||||
94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F
|
||||
9E4AFF73
|
||||
`)
|
||||
|
||||
knownGroups[4096] = createParams(5, 4096, crypto.SHA256, `
|
||||
FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
|
||||
8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
|
||||
302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
|
||||
A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
|
||||
49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
|
||||
FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
|
||||
670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
|
||||
180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
|
||||
3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
|
||||
04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
|
||||
B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
|
||||
1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
|
||||
BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
|
||||
E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26
|
||||
99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB
|
||||
04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2
|
||||
233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127
|
||||
D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199
|
||||
FFFFFFFF FFFFFFFF
|
||||
`)
|
||||
}
|
||||
104
cli/packages/srp/server.go
Normal file
104
cli/packages/srp/server.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type SRPServer struct {
|
||||
Params *SRPParams
|
||||
Verifier *big.Int
|
||||
Secret2 *big.Int
|
||||
B *big.Int
|
||||
M1 []byte
|
||||
M2 []byte
|
||||
K []byte
|
||||
u *big.Int
|
||||
s *big.Int
|
||||
}
|
||||
|
||||
func NewServer(params *SRPParams, Vb []byte, S2b []byte) *SRPServer {
|
||||
multiplier := getMultiplier(params)
|
||||
V := intFromBytes(Vb)
|
||||
secret2 := intFromBytes(S2b)
|
||||
|
||||
Bb := getB(params, multiplier, V, secret2)
|
||||
B := intFromBytes(Bb)
|
||||
|
||||
return &SRPServer{
|
||||
Params: params,
|
||||
Secret2: secret2,
|
||||
Verifier: V,
|
||||
B: B,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SRPServer) ComputeB() []byte {
|
||||
return intToBytes(s.B)
|
||||
}
|
||||
|
||||
func (s *SRPServer) SetA(A []byte) {
|
||||
AInt := intFromBytes(A)
|
||||
U := getu(s.Params, AInt, s.B)
|
||||
S := serverGetS(s.Params, s.Verifier, AInt, s.Secret2, U)
|
||||
|
||||
s.K = getK(s.Params, S)
|
||||
s.M1 = getM1(s.Params, A, intToBytes(s.B), S)
|
||||
s.M2 = getM2(s.Params, A, s.M1, s.K)
|
||||
|
||||
s.u = U // only for tests
|
||||
s.s = intFromBytes(S) // only for tests
|
||||
}
|
||||
|
||||
func (s *SRPServer) CheckM1(M1 []byte) ([]byte, error) {
|
||||
if !bytes.Equal(s.M1, M1) {
|
||||
return nil, errors.New("Client did not use the same password")
|
||||
} else {
|
||||
return s.M2, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SRPServer) ComputeK() []byte {
|
||||
return s.K
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func serverGetS(params *SRPParams, V, A, S2, U *big.Int) []byte {
|
||||
ALessThan0 := A.Cmp(big.NewInt(0)) <= 0
|
||||
NLessThanA := params.N.Cmp(A) <= 0
|
||||
if ALessThan0 || NLessThanA {
|
||||
panic("invalid client-supplied 'A', must be 1..N-1")
|
||||
}
|
||||
|
||||
result1 := new(big.Int)
|
||||
result1.Exp(V, U, params.N)
|
||||
|
||||
result2 := new(big.Int)
|
||||
result2.Mul(A, result1)
|
||||
|
||||
result3 := new(big.Int)
|
||||
result3.Exp(result2, S2, params.N)
|
||||
|
||||
result4 := new(big.Int)
|
||||
result4.Mod(result3, params.N)
|
||||
|
||||
return padToN(result4, params)
|
||||
}
|
||||
|
||||
func getB(params *SRPParams, multiplier, V, b *big.Int) []byte {
|
||||
gModPowB := new(big.Int)
|
||||
gModPowB.Exp(params.G, b, params.N)
|
||||
|
||||
kMulV := new(big.Int)
|
||||
kMulV.Mul(multiplier, V)
|
||||
|
||||
leftSide := new(big.Int)
|
||||
leftSide.Add(kMulV, gModPowB)
|
||||
|
||||
final := new(big.Int)
|
||||
final.Mod(leftSide, params.N)
|
||||
|
||||
return padToN(final, params)
|
||||
}
|
||||
103
cli/packages/srp/srp.go
Normal file
103
cli/packages/srp/srp.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// Package srp is port of node-srp to Go.
|
||||
//
|
||||
// To use SRP, first decide on they parameters you will use. Both client and server must
|
||||
// use the same set.
|
||||
//
|
||||
// params := srp.GetParams(4096)
|
||||
//
|
||||
// From the client... generate a new secret key, initialize the client, and compute A.
|
||||
// Once you have A, you can send A to the server.
|
||||
//
|
||||
// secret1 := srp.GenKey()
|
||||
// client := NewClient(params, salt, identity, secret, a)
|
||||
// srpA := client.computeA()
|
||||
//
|
||||
// sendToServer(srpA)
|
||||
//
|
||||
// From the server... generate another secret key, initialize the server, and compute B.
|
||||
// Once you have B, you can send B to the client.
|
||||
//
|
||||
// secret2 := srp.GenKey()
|
||||
// server := NewServer(params, verifier, secret2)
|
||||
// srpB := client.computeB()
|
||||
//
|
||||
// sendToClient(srpB)
|
||||
//
|
||||
// Once the client received B from the server, it can compute M1 based on A and B.
|
||||
// Once you have M1, send M1 to the server.
|
||||
//
|
||||
// client.setB(srpB)
|
||||
// srpM1 := client.ComputeM1()
|
||||
// sendM1ToServer(srpM1)
|
||||
//
|
||||
// Once the server receives M1, it can verify that it is correct. If checkM1() returns
|
||||
// an error, authentication failed. If it succeeds it should be sent to the client.
|
||||
//
|
||||
// srpM2, err := server.checkM1(srpM1)
|
||||
//
|
||||
// Once the client receives M2, it can verify that it is correct, and know that authentication
|
||||
// was successful.
|
||||
//
|
||||
// err = client.CheckM2(serverM2)
|
||||
//
|
||||
// Now that both client and server have completed a successful authentication, they can
|
||||
// both compute K independently. K can now be used as either a key to encrypt communication
|
||||
// or as a session ID.
|
||||
//
|
||||
// clientK := client.ComputeK()
|
||||
// serverK := server.ComputeK()
|
||||
package srp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func GenKey() []byte {
|
||||
bytes := make([]byte, 32)
|
||||
_, err := io.ReadFull(rand.Reader, bytes)
|
||||
if err != nil {
|
||||
panic("Random source is broken!")
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
func getK(params *SRPParams, S []byte) []byte {
|
||||
hashK := params.Hash.New()
|
||||
hashK.Write(S)
|
||||
return hashToBytes(hashK)
|
||||
}
|
||||
|
||||
func getu(params *SRPParams, A, B *big.Int) *big.Int {
|
||||
hashU := params.Hash.New()
|
||||
hashU.Write(A.Bytes())
|
||||
hashU.Write(B.Bytes())
|
||||
|
||||
return hashToInt(hashU)
|
||||
}
|
||||
|
||||
func getM1(params *SRPParams, A, B, S []byte) []byte {
|
||||
hashM1 := params.Hash.New()
|
||||
hashM1.Write(A)
|
||||
hashM1.Write(B)
|
||||
hashM1.Write(S)
|
||||
return hashToBytes(hashM1)
|
||||
}
|
||||
|
||||
func getM2(params *SRPParams, A, M, K []byte) []byte {
|
||||
hashM1 := params.Hash.New()
|
||||
hashM1.Write(A)
|
||||
hashM1.Write(M)
|
||||
hashM1.Write(K)
|
||||
return hashToBytes(hashM1)
|
||||
}
|
||||
|
||||
func getMultiplier(params *SRPParams) *big.Int {
|
||||
hashK := params.Hash.New()
|
||||
hashK.Write(padToN(params.N, params))
|
||||
hashK.Write(padToN(params.G, params))
|
||||
|
||||
return hashToInt(hashK)
|
||||
}
|
||||
48
cli/packages/srp/util.go
Normal file
48
cli/packages/srp/util.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"math/big"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Helpers
|
||||
|
||||
func padTo(bytes []byte, length int) []byte {
|
||||
paddingLength := length - len(bytes)
|
||||
padding := make([]byte, paddingLength, paddingLength)
|
||||
|
||||
return append(padding, bytes...)
|
||||
}
|
||||
|
||||
func padToN(number *big.Int, params *SRPParams) []byte {
|
||||
return padTo(number.Bytes(), params.NLengthBits/8)
|
||||
}
|
||||
|
||||
func hashToBytes(h hash.Hash) []byte {
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func hashToInt(h hash.Hash) *big.Int {
|
||||
U := new(big.Int)
|
||||
U.SetBytes(hashToBytes(h))
|
||||
return U
|
||||
}
|
||||
|
||||
func intFromBytes(bytes []byte) *big.Int {
|
||||
i := new(big.Int)
|
||||
i.SetBytes(bytes)
|
||||
return i
|
||||
}
|
||||
|
||||
func intToBytes(i *big.Int) []byte {
|
||||
return i.Bytes()
|
||||
}
|
||||
|
||||
func bytesFromHexString(s string) []byte {
|
||||
re, _ := regexp.Compile("[^0-9a-fA-F]")
|
||||
h := re.ReplaceAll([]byte(s), []byte(""))
|
||||
b, _ := hex.DecodeString(string(h))
|
||||
return b
|
||||
}
|
||||
29
cli/packages/util/common.go
Normal file
29
cli/packages/util/common.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
CONFIG_FILE_NAME = "infisical-config.json"
|
||||
CONFIG_FOLDER_NAME = ".infisical"
|
||||
INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json"
|
||||
INFISICAL_SERVICE_TOKEN = "INFISICAL_SERVICE_TOKEN"
|
||||
)
|
||||
|
||||
var INFISICAL_URL = "https://api.infisical.com"
|
||||
|
||||
func GetHomeDir() (string, error) {
|
||||
directory, err := os.UserHomeDir()
|
||||
return directory, err
|
||||
}
|
||||
|
||||
func WriteToFile(fileName string, dataToWrite []byte, filePerm os.FileMode) error {
|
||||
err := os.WriteFile(fileName, dataToWrite, filePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to wrote to file", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
92
cli/packages/util/config.go
Normal file
92
cli/packages/util/config.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func WriteInitalConfig(userCredentials *models.UserCredentials) error {
|
||||
fullConfigFilePath, fullConfigFileDirPath, err := GetFullConfigFilePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create directory
|
||||
if _, err := os.Stat(fullConfigFileDirPath); errors.Is(err, os.ErrNotExist) {
|
||||
err := os.Mkdir(fullConfigFileDirPath, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configFile := models.ConfigFile{
|
||||
LoggedInUserEmail: userCredentials.Email,
|
||||
}
|
||||
|
||||
configFileMarshalled, err := json.Marshal(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create file in directory
|
||||
err = WriteToFile(fullConfigFilePath, configFileMarshalled, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func ConfigFileExists() bool {
|
||||
fullConfigFileURI, _, err := GetFullConfigFilePath()
|
||||
if err != nil {
|
||||
log.Debugln("There was an error when creating the full path to config file", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := os.Stat(fullConfigFileURI); err == nil {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func WorkspaceConfigFileExists() bool {
|
||||
if _, err := os.Stat(INFISICAL_WORKSPACE_CONFIG_FILE_NAME); err == nil {
|
||||
return true
|
||||
} else {
|
||||
log.Debugln(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func GetWorkSpaceFromFile() (models.WorkspaceConfigFile, error) {
|
||||
configFileAsBytes, err := os.ReadFile(INFISICAL_WORKSPACE_CONFIG_FILE_NAME)
|
||||
if err != nil {
|
||||
return models.WorkspaceConfigFile{}, err
|
||||
}
|
||||
|
||||
var workspaceConfigFile models.WorkspaceConfigFile
|
||||
err = json.Unmarshal(configFileAsBytes, &workspaceConfigFile)
|
||||
if err != nil {
|
||||
return models.WorkspaceConfigFile{}, err
|
||||
}
|
||||
|
||||
return workspaceConfigFile, nil
|
||||
}
|
||||
|
||||
func GetFullConfigFilePath() (fullPathToFile string, fullPathToDirectory string, err error) {
|
||||
homeDir, err := GetHomeDir()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
fullPath := fmt.Sprintf("%s/%s/%s", homeDir, CONFIG_FOLDER_NAME, CONFIG_FILE_NAME)
|
||||
fullDirPath := fmt.Sprintf("%s/%s", homeDir, CONFIG_FOLDER_NAME)
|
||||
return fullPath, fullDirPath, err
|
||||
}
|
||||
99
cli/packages/util/credentials.go
Normal file
99
cli/packages/util/credentials.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zalando/go-keyring"
|
||||
)
|
||||
|
||||
const SERVICE_NAME = "infisical"
|
||||
|
||||
// To do: what happens if the user doesn't have a keyring in their system?
|
||||
func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error {
|
||||
userCredMarshalled, err := json.Marshal(userCred)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Something went wrong when marshalling user creds:", err)
|
||||
}
|
||||
|
||||
err = keyring.Set(SERVICE_NAME, userCred.Email, string(userCredMarshalled))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to store user credentials:", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) {
|
||||
credentialsString, err := keyring.Get(SERVICE_NAME, userEmail)
|
||||
if err != nil {
|
||||
return models.UserCredentials{}, fmt.Errorf("Unable to get key from Keyring:", err)
|
||||
}
|
||||
|
||||
var userCredentials models.UserCredentials
|
||||
|
||||
err = json.Unmarshal([]byte(credentialsString), &userCredentials)
|
||||
if err != nil {
|
||||
return models.UserCredentials{}, fmt.Errorf("Something went wrong when unmarshalling user creds:", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return models.UserCredentials{}, fmt.Errorf("Unable to store user credentials", err)
|
||||
}
|
||||
|
||||
return userCredentials, err
|
||||
}
|
||||
|
||||
func IsUserLoggedIn() (hasUserLoggedIn bool, theUsersEmail string, err error) {
|
||||
if ConfigFileExists() {
|
||||
fullConfigFilePath, _, err := GetFullConfigFilePath()
|
||||
if err != nil {
|
||||
log.Debugln("Error gettting full path:", err)
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
configFileAsBytes, err := os.ReadFile(fullConfigFilePath)
|
||||
if err != nil {
|
||||
log.Debugln("Unable to read config file:", err)
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
var configFile models.ConfigFile
|
||||
err = json.Unmarshal(configFileAsBytes, &configFile)
|
||||
if err != nil {
|
||||
log.Debugln("Unable to unmarshal config file:", err)
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
userCreds, err := GetUserCredsFromKeyRing(configFile.LoggedInUserEmail)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
// check to to see if the JWT is still valid
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(userCreds.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
response, err := httpClient.
|
||||
R().
|
||||
Post(fmt.Sprintf("%v/%v", INFISICAL_URL, "checkAuth"))
|
||||
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
if response.StatusCode() > 299 {
|
||||
log.Infoln("Login expired, please login again.")
|
||||
return false, "", fmt.Errorf("Login expired, please login again.")
|
||||
}
|
||||
|
||||
return true, configFile.LoggedInUserEmail, nil
|
||||
} else {
|
||||
return false, "", nil
|
||||
}
|
||||
}
|
||||
31
cli/packages/util/crypto.go
Normal file
31
cli/packages/util/crypto.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func DecryptSymmetric(key []byte, encryptedPrivateKey []byte, tag []byte, IV []byte) ([]byte, error) {
|
||||
log.Debugln("Key:", key, "encryptedPrivateKey", encryptedPrivateKey, "tag", tag, "IV", IV)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesgcm, err := cipher.NewGCMWithNonceSize(block, len(IV))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nonce = IV
|
||||
var ciphertext = append(encryptedPrivateKey, tag...)
|
||||
|
||||
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
205
cli/packages/util/secrets.go
Normal file
205
cli/packages/util/secrets.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
|
||||
func GetSecretsFromAPIUsingCurrentLoggedInUser(stageName string, userCreds models.UserCredentials) ([]models.SingleEnvironmentVariable, error) {
|
||||
log.Debugln("stageName", stageName, "userCreds", userCreds)
|
||||
// check if user has configured a workspace
|
||||
workspace, err := GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to read workspace file:", err)
|
||||
}
|
||||
|
||||
// create http client
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(userCreds.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
var pullSecretsRequestResponse models.PullSecretsResponse
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetQueryParam("environment", stageName).
|
||||
SetQueryParam("channel", "cli").
|
||||
SetResult(&pullSecretsRequestResponse).
|
||||
Get(fmt.Sprintf("%v/%v/%v", INFISICAL_URL, "secret", workspace.WorkspaceId)) // need to change workspace id
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode() > 299 {
|
||||
log.Debugln(response)
|
||||
return nil, fmt.Errorf(response.Status())
|
||||
}
|
||||
|
||||
// Get workspace key
|
||||
workspaceKey, err := base64.StdEncoding.DecodeString(pullSecretsRequestResponse.Key.EncryptedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, err := base64.StdEncoding.DecodeString(pullSecretsRequestResponse.Key.Nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
senderPublicKey, err := base64.StdEncoding.DecodeString(pullSecretsRequestResponse.Key.Sender.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentUsersPrivateKey, err := base64.StdEncoding.DecodeString(userCreds.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugln("workspaceKey", workspaceKey, "nonce", nonce, "senderPublicKey", senderPublicKey, "currentUsersPrivateKey", currentUsersPrivateKey)
|
||||
workspaceKeyInBytes, _ := box.Open(nil, workspaceKey, (*[24]byte)(nonce), (*[32]byte)(senderPublicKey), (*[32]byte)(currentUsersPrivateKey))
|
||||
var listOfEnv []models.SingleEnvironmentVariable
|
||||
|
||||
for _, secret := range pullSecretsRequestResponse.Secrets {
|
||||
key_iv, _ := base64.StdEncoding.DecodeString(secret.SecretKeyIV)
|
||||
key_tag, _ := base64.StdEncoding.DecodeString(secret.SecretKeyTag)
|
||||
key_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretKeyCiphertext)
|
||||
|
||||
plainTextKey, err := DecryptSymmetric(workspaceKeyInBytes, key_ciphertext, key_tag, key_iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value_iv, _ := base64.StdEncoding.DecodeString(secret.SecretValueIV)
|
||||
value_tag, _ := base64.StdEncoding.DecodeString(secret.SecretValueTag)
|
||||
value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValueCiphertext)
|
||||
|
||||
plainTextValue, err := DecryptSymmetric(workspaceKeyInBytes, value_ciphertext, value_tag, value_iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env := models.SingleEnvironmentVariable{
|
||||
Key: string(plainTextKey),
|
||||
Value: string(plainTextValue),
|
||||
}
|
||||
|
||||
listOfEnv = append(listOfEnv, env)
|
||||
}
|
||||
|
||||
return listOfEnv, nil
|
||||
}
|
||||
|
||||
func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, stageName string, projectId string) ([]models.SingleEnvironmentVariable, error) {
|
||||
if infisicalToken == "" || projectId == "" || stageName == "" {
|
||||
return nil, errors.New("infisical token, project id and or stage name cannot be empty")
|
||||
}
|
||||
splitToken := strings.Split(infisicalToken, ",")
|
||||
JTWToken := splitToken[0]
|
||||
temPrivateKey := splitToken[1]
|
||||
|
||||
// create http client
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
var pullSecretsByInfisicalTokenResponse models.PullSecretsByInfisicalTokenResponse
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetQueryParam("environment", stageName).
|
||||
SetQueryParam("channel", "cli").
|
||||
SetResult(&pullSecretsByInfisicalTokenResponse).
|
||||
Get(fmt.Sprintf("%v/secret/%v/service-token", INFISICAL_URL, projectId))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode() > 299 {
|
||||
log.Debugln(response)
|
||||
return nil, fmt.Errorf(response.Status())
|
||||
}
|
||||
|
||||
// Get workspace key
|
||||
workspaceKey, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.EncryptedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.Nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
senderPublicKey, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.Sender.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentUsersPrivateKey, err := base64.StdEncoding.DecodeString(temPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workspaceKeyInBytes, _ := box.Open(nil, workspaceKey, (*[24]byte)(nonce), (*[32]byte)(senderPublicKey), (*[32]byte)(currentUsersPrivateKey))
|
||||
var listOfEnv []models.SingleEnvironmentVariable
|
||||
|
||||
for _, secret := range pullSecretsByInfisicalTokenResponse.Secrets {
|
||||
key_iv, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Iv)
|
||||
key_tag, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Tag)
|
||||
key_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Ciphertext)
|
||||
|
||||
plainTextKey, err := DecryptSymmetric(workspaceKeyInBytes, key_ciphertext, key_tag, key_iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value_iv, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Iv)
|
||||
value_tag, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Tag)
|
||||
value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Ciphertext)
|
||||
|
||||
plainTextValue, err := DecryptSymmetric(workspaceKeyInBytes, value_ciphertext, value_tag, value_iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env := models.SingleEnvironmentVariable{
|
||||
Key: string(plainTextKey),
|
||||
Value: string(plainTextValue),
|
||||
}
|
||||
|
||||
listOfEnv = append(listOfEnv, env)
|
||||
}
|
||||
|
||||
return listOfEnv, nil
|
||||
}
|
||||
|
||||
func GetWorkSpacesFromAPI(userCreds models.UserCredentials) (workspaces []models.Workspace, err error) {
|
||||
// create http client
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(userCreds.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
var getWorkSpacesResponse models.GetWorkSpacesResponse
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetResult(&getWorkSpacesResponse).
|
||||
Get(fmt.Sprintf("%v/%v", INFISICAL_URL, "workspace"))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode() > 299 {
|
||||
return nil, fmt.Errorf("ops, unsuccessful response code. [response=%v]", response)
|
||||
}
|
||||
|
||||
return getWorkSpacesResponse.Workspaces, nil
|
||||
}
|
||||
Reference in New Issue
Block a user