Add frontend, backend and CLI

This commit is contained in:
Maidul Islam
2022-11-17 17:54:35 -05:00
parent 8d00c5cdb7
commit bea0ff6e05
276 changed files with 50230 additions and 72 deletions

2
cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.infisical.json
dist/

102
cli/README.md Normal file
View File

@@ -0,0 +1,102 @@
## Install
#### Windows
Use [Scoop](https://scoop.sh/) package manager
```
$ scoop bucket add org https://github.com/Infisical/scoop-infisical.git
$ scoop install infisical
$ infisical --version
```
To update:
```
$ scoop update infisical
```
#### Mac OS
Use [brew](https://brew.sh/) package manager
```
$ brew install infisical/get-cli/infisical
$ infisical --version
```
To update:
```
$ brew upgrade infisical
```
#### Linux
##### Debian/Ubuntu (package manager: apt)
```
Add Infisical apt repo
$ echo "deb [trusted=yes] https://apt.fury.io/infisical/ /" | tee -a /etc/apt/sources.list.d/infisical.list
Add prerequisites
$ apt update && apt -y install ca-certificates sudo
Install infisical cli
$ sudo apt update && apt install infisical
To make sure the CLI has been installed, you may run this command.
$ infisical --version
```
We do not yet have repositores setup for APK, YUM and APT package managers. However, we have several binaries which can be downloaded manually for your Linux. Please vist the [release age](https://github.com/Infisical/infisical/releases)
#### Install via bash and curl
This script will attempt to download the correct version of Infisical CLI and add it to your path. No package manager needed.
```
curl https://raw.githubusercontent.com/Infisical/infisical/main/scripts/install.sh | sh
```
## Local Usage
Once you have the CLI installed, using it is easy.
#### Steps 1
Create a project at https://infisical.com/ if you haven't already add your secrets to it.
#### Step 2
Login to the CLI by running the following command in your terminal
```
infisical login
```
#### Step 3
After logging in, `CD` to the root of the project where you would like to inject your secrets into. Once you are in the root, run the following command in the terminal to link your Infisical project to your local project.
```
infisical init
```
#### Step 3
To inject the secrets from the project you have selected into your application process, run the following command.
```
infisical run -- <your application start command>
```
Example:
```
infisical run -- npm run dev
```
## General production Usage
Once you have the binary installed in your production environment, injecting secrets is easy.
#### Steps 1
Get a Infisical Token for your project by visiting BLANK. Also note down the project ID for which you created the token for.
#### Steps 2
Ensure your application has the environment variable `INFISICAL_TOKEN` asigned to the token you received in step one. Then run
```
infisical run --projectId=<projectID> -- <your application start command>
```

26
cli/go.mod Normal file
View File

@@ -0,0 +1,26 @@
module github.com/Infisical/infisical-merge
go 1.19
require (
github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.2.0
)
require (
github.com/alessio/shellescape v1.4.1 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/danieljoos/wincred v1.1.0 // indirect
github.com/godbus/dbus/v5 v5.0.6 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect
)
require (
github.com/go-resty/resty/v2 v2.7.0
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/manifoldco/promptui v0.9.0
github.com/sirupsen/logrus v1.9.0
github.com/spf13/pflag v1.0.5 // indirect
github.com/zalando/go-keyring v0.2.1
)

58
cli/go.sum Normal file
View File

@@ -0,0 +1,58 @@
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc=
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE=
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,4 @@
FROM alpine
RUN apk add --no-cache tini
COPY infisical /bin/infisical
ENTRYPOINT ["/sbin/tini", "--", "/bin/infisical"]

5
cli/infisical-cli.repo Normal file
View File

@@ -0,0 +1,5 @@
[infisical]
name=Infisical CLI
baseurl=https://yum.fury.io/infisical/
enabled=1
gpgcheck=0

10
cli/main.go Normal file
View File

@@ -0,0 +1,10 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package main
import "github.com/Infisical/infisical-merge/packages/cmd"
func main() {
cmd.Execute()
}

130
cli/packages/cmd/init.go Normal file
View 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
}

View 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
View 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
View 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
View 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
View 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"`
}

View 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
View 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)
}

View 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
View 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
View 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
View 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
}

View 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
}

View 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
}

View 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
}
}

View 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
}

View 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
}

97
cli/scripts/install.sh Executable file
View File

@@ -0,0 +1,97 @@
#!/bin/bash
PLATFORM=
ARCH=
TEMP_DOWNLOAD_FOLDER=
function delete_temp_install_folder()
{
$(rm -rf $TEMP_DOWNLOAD_FOLDER 2> /dev/null)
}
# platform
case "$(uname -s)" in
Linux) PLATFORM='linux';;
Darwin) PLATFORM='darwin';;
CYGWIN*|MINGW*|MSYS*) PLATFORM='windows';;
FreeBSD) PLATFORM='freebsd';;
*)
echo "Your platform doesn't seem to be of type darwin, linux or windows"
echo "Your architecture is $(uname -m) and your platform is $(uname -s)"
exit 1
;;
esac
# architecture
if [[ "$(uname -m)" == 'x86_64' || "$(uname -m)" == "amd64" ]]; then
ARCH="amd64"
elif [[ "$(uname -m)" == armv5* ]]; then
ARCH="armv5"
elif [[ "$(uname -m)" == armv6* ]]; then
ARCH="armv6"
elif [[ "$(uname -m)" == armv7* ]]; then
ARCH="armv7"
elif [[ "$(uname -m)" == 'arm64' || "$(uname -m)" == 'aarch64' ]]; then
ARCH="arm64"
elif [[ "$(uname -m)" == "i386" || "$(uname -m)" == "i686" ]]; then
ARCH="i386"
else
echo >&2 "Your architecture doesn't seem to supported. Your architecture is $(uname -m) and your platform is $(uname -s)"
exit 1
fi
# Credit https://stackoverflow.com/questions/20010199/how-to-determine-if-a-process-runs-inside-lxc-docker
if [[ "$(cat /proc/1/cgroup 2> /dev/null | grep docker | wc -l)" > 0 ]] || [ -f /.dockerenv ]; then
IS_RUNNING_IN_DOCKER=true
else
IS_RUNNING_IN_DOCKER=false
fi
# example: v0.0.98
LATEST_RELEASE_VERSION=$(curl -s "https://api.github.com/repos/Infisical/infisical/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
# example: 0.0.98
NUMERIC_RELEASE_VERSION="${LATEST_RELEASE_VERSION:1}"
DOWNLOAD_LINK="https://github.com/Infisical/infisical/releases/download/${LATEST_RELEASE_VERSION}/infisical_${NUMERIC_RELEASE_VERSION}_${PLATFORM}_${ARCH}.tar.gz"
CHECK_IF_BINARY_EXISTS=$(curl -s -o -L /dev/null -w "%{http_code}" ${DOWNLOAD_LINK})
if [[ $CHECK_IF_BINARY_EXISTS == "000Not Found404" ]]; then
echo "Looks like we do not yet have a binary for this architecture and platform."
echo "Your architecture is $(uname -m) and your platform is $(uname -s)"
exit 1
fi
# make temp install folder
mkdir -p infisical_temp_download_folder
cd infisical_temp_download_folder
TEMP_DOWNLOAD_FOLDER=$(pwd)
# download latest cli
curl -L -o infisical-binary.tar.gz ${DOWNLOAD_LINK}
# open up the tar file
tar zxf infisical-binary.tar.gz
if [ "$PLATFORM" == "darwin" ] || [ $RUNNING_IN_DOCKER ] ; then
if [[ -d /usr/local/bin ]]; then
mv infisical /usr/local/bin/
echo "Infisical CLI ${LATEST_RELEASE_VERSION} has been installed in /usr/local/bin."
else
echo >&2 "Error: /usr/local/bin does not exist. You must create it before reinstalling"
delete_temp_install_folder
exit 1
fi
elif [ "$PLATFORM" == "windows" ]; then
mkdir $HOME/bin 2> /dev/null
mv infisical.exe $HOME/bin/
echo "Infisical CLI ${LATEST_RELEASE_VERSION} has been installed in $HOME/bin"
echo "Please add $HOME/bin to your system PATH"
else
sudo mv infisical /usr/local/bin/
echo "Infisical CLI ${LATEST_RELEASE_VERSION} has been installed in /usr/local/bin."
fi
delete_temp_install_folder

14
cli/upload_to_cloudfront.sh Executable file
View File

@@ -0,0 +1,14 @@
for i in *.apk; do
[ -f "$i" ] || break
cloudsmith push alpine infisical/infisical-cli/alpine/any-version $i
done
for i in *.deb; do
[ -f "$i" ] || break
cloudsmith push deb --no-republish infisical/infisical-cli/debian/any-version $i
done
for i in *.rpm; do
[ -f "$i" ] || break
cloudsmith push rpm --no-republish infisical/infisical-cli/any-distro/any-version $i
done