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:
2
cli/.gitignore
vendored
Normal file
2
cli/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.infisical.json
|
||||
dist/
|
||||
102
cli/README.md
Normal file
102
cli/README.md
Normal 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
26
cli/go.mod
Normal 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
58
cli/go.sum
Normal 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=
|
||||
4
cli/goreleaser.dockerfile
Normal file
4
cli/goreleaser.dockerfile
Normal 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
5
cli/infisical-cli.repo
Normal 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
10
cli/main.go
Normal 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
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
|
||||
}
|
||||
97
cli/scripts/install.sh
Executable file
97
cli/scripts/install.sh
Executable 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
14
cli/upload_to_cloudfront.sh
Executable 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
|
||||
Reference in New Issue
Block a user