diff --git a/cli/go.mod b/cli/go.mod index 956e9bb29..6b7d92489 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -13,7 +13,6 @@ require ( require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/Luzifer/go-openssl/v4 v4.1.0 // indirect github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -22,6 +21,8 @@ require ( github.com/go-openapi/strfmt v0.21.3 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/mtibben/percent v0.2.1 // indirect @@ -35,7 +36,7 @@ require ( ) require ( - github.com/Luzifer/go-openssl v2.0.0+incompatible + github.com/fatih/color v1.13.0 github.com/go-resty/resty/v2 v2.7.0 github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible diff --git a/cli/go.sum b/cli/go.sum index 2b8836515..f7e17c62a 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -2,10 +2,6 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= -github.com/Luzifer/go-openssl v2.0.0+incompatible h1:EpNNxrPDji4rRzE0KeOeIeV7pHyKe8zF9oNnAXy4mBY= -github.com/Luzifer/go-openssl v2.0.0+incompatible/go.mod h1:t2qnLjT8WQ3usGU1R8uAqjY4T7CK7eMg9vhQ3l9Ue/Y= -github.com/Luzifer/go-openssl/v4 v4.1.0 h1:8qi3Z6f8Aflwub/Cs4FVSmKUEg/lC8GlODbR2TyZ+nM= -github.com/Luzifer/go-openssl/v4 v4.1.0/go.mod h1:3i1T3Pe6eQK19d86WhuQzjLyMwBaNmGmt3ZceWpWVa4= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -26,6 +22,8 @@ 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/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= @@ -53,6 +51,11 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= @@ -100,23 +103,21 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/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/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -125,7 +126,6 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/cli/packages/api/api.go b/cli/packages/api/api.go new file mode 100644 index 000000000..782fa143c --- /dev/null +++ b/cli/packages/api/api.go @@ -0,0 +1,136 @@ +package api + +import ( + "fmt" + + "github.com/Infisical/infisical-merge/packages/config" + "github.com/go-resty/resty/v2" +) + +func CallBatchModifySecretsByWorkspaceAndEnv(httpClient *resty.Client, request BatchModifySecretsByWorkspaceAndEnvRequest) error { + endpoint := fmt.Sprintf("%v/v2/secret/batch-modify/workspace/%v/environment/%v", config.INFISICAL_URL, request.WorkspaceId, request.EnvironmentName) + response, err := httpClient. + R(). + SetBody(request). + Patch(endpoint) + + if err != nil { + return fmt.Errorf("CallBatchModifySecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err) + } + + if response.StatusCode() > 299 { + return fmt.Errorf("CallBatchModifySecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response) + } + + return nil +} + +func CallBatchCreateSecretsByWorkspaceAndEnv(httpClient *resty.Client, request BatchCreateSecretsByWorkspaceAndEnvRequest) error { + endpoint := fmt.Sprintf("%v/v2/secret/batch-create/workspace/%v/environment/%v", config.INFISICAL_URL, request.WorkspaceId, request.EnvironmentName) + response, err := httpClient. + R(). + SetBody(request). + Post(endpoint) + + if err != nil { + return fmt.Errorf("CallBatchCreateSecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err) + } + + if response.StatusCode() > 299 { + return fmt.Errorf("CallBatchCreateSecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response) + } + + return nil +} + +func CallBatchDeleteSecretsByWorkspaceAndEnv(httpClient *resty.Client, request BatchDeleteSecretsBySecretIdsRequest) error { + endpoint := fmt.Sprintf("%v/v2/secret/batch/workspace/%v/environment/%v", config.INFISICAL_URL, request.WorkspaceId, request.EnvironmentName) + response, err := httpClient. + R(). + SetBody(request). + Delete(endpoint) + + if err != nil { + return fmt.Errorf("CallBatchDeleteSecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err) + } + + if response.StatusCode() > 299 { + return fmt.Errorf("CallBatchDeleteSecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response) + } + + return nil +} + +func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncryptedWorkspaceKeyRequest) (GetEncryptedWorkspaceKeyResponse, error) { + endpoint := fmt.Sprintf("%v/v2/workspace/%v/encrypted-key", config.INFISICAL_URL, request.WorkspaceId) + var result GetEncryptedWorkspaceKeyResponse + response, err := httpClient. + R(). + SetResult(&result). + Get(endpoint) + + if err != nil { + return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err) + } + + if response.StatusCode() > 299 { + return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response: [response=%s]", response) + } + + return result, nil +} + +func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDetailsResponse, error) { + var tokenDetailsResponse GetServiceTokenDetailsResponse + response, err := httpClient. + R(). + SetResult(&tokenDetailsResponse). + Get(fmt.Sprintf("%v/v2/service-token", config.INFISICAL_URL)) + + if err != nil { + return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unable to complete api request [err=%s]", err) + } + + if response.StatusCode() > 299 { + return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response) + } + + return tokenDetailsResponse, nil +} + +func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Request) (GetEncryptedSecretsV2Response, error) { + var secretsResponse GetEncryptedSecretsV2Response + response, err := httpClient. + R(). + SetResult(&secretsResponse). + SetQueryParam("environment", request.EnvironmentName). + Get(fmt.Sprintf("%v/v2/secret/workspace/%v", config.INFISICAL_URL, request.WorkspaceId)) + + if err != nil { + return GetEncryptedSecretsV2Response{}, fmt.Errorf("CallGetSecretsV2: Unable to complete api request [err=%s]", err) + } + + if response.StatusCode() > 299 { + return GetEncryptedSecretsV2Response{}, fmt.Errorf("CallGetSecretsV2: Unsuccessful response: [response=%s]", response) + } + + return secretsResponse, nil +} + +func CallGetAllWorkSpacesUserBelongsTo(httpClient *resty.Client) (GetWorkSpacesResponse, error) { + var workSpacesResponse GetWorkSpacesResponse + response, err := httpClient. + R(). + SetResult(&workSpacesResponse). + Get(fmt.Sprintf("%v/v1/workspace", config.INFISICAL_URL)) + + if err != nil { + return GetWorkSpacesResponse{}, err + } + + if response.StatusCode() > 299 { + return GetWorkSpacesResponse{}, fmt.Errorf("CallGetAllWorkSpacesUserBelongsTo: Unsuccessful response: [response=%v]", response) + } + + return workSpacesResponse, nil +} diff --git a/cli/packages/models/api.go b/cli/packages/api/model.go similarity index 69% rename from cli/packages/models/api.go rename to cli/packages/api/model.go index d17200b87..b891a4af0 100644 --- a/cli/packages/models/api.go +++ b/cli/packages/api/model.go @@ -1,4 +1,4 @@ -package models +package api import "time" @@ -119,14 +119,13 @@ type PullSecretsByInfisicalTokenResponse struct { } 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"` + Workspaces []struct { + ID string `json:"_id"` + Name string `json:"name"` + Plan string `json:"plan,omitempty"` + V int `json:"__v"` + Organization string `json:"organization,omitempty"` + } `json:"workspaces"` } type Secret struct { @@ -169,30 +168,68 @@ type GetEncryptedWorkspaceKeyRequest struct { } type GetEncryptedWorkspaceKeyResponse struct { - LatestKey struct { - ID string `json:"_id"` - EncryptedKey string `json:"encryptedKey"` - Nonce string `json:"nonce"` - Sender struct { - ID string `json:"_id"` - Email string `json:"email"` - RefreshVersion int `json:"refreshVersion"` - 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:"latestKey"` + ID string `json:"_id"` + EncryptedKey string `json:"encryptedKey"` + Nonce string `json:"nonce"` + Sender struct { + ID string `json:"_id"` + Email string `json:"email"` + RefreshVersion int `json:"refreshVersion"` + 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"` } type GetSecretsByWorkspaceIdAndEnvironmentRequest struct { EnvironmentName string `json:"environmentName"` WorkspaceId string `json:"workspaceId"` } + +type GetEncryptedSecretsV2Request struct { + EnvironmentName string `json:"environmentName"` + WorkspaceId string `json:"workspaceId"` +} + +type GetEncryptedSecretsV2Response []struct { + ID string `json:"_id"` + Version int `json:"version"` + 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"` + SecretCommentCiphertext string `json:"secretCommentCiphertext"` + SecretCommentIV string `json:"secretCommentIV"` + SecretCommentTag string `json:"secretCommentTag"` + SecretCommentHash string `json:"secretCommentHash"` + V int `json:"__v"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + User string `json:"user,omitempty"` +} + +type GetServiceTokenDetailsResponse struct { + ID string `json:"_id"` + Name string `json:"name"` + Workspace string `json:"workspace"` + Environment string `json:"environment"` + User string `json:"user"` + EncryptedKey string `json:"encryptedKey"` + Iv string `json:"iv"` + Tag string `json:"tag"` +} diff --git a/cli/packages/cmd/export.go b/cli/packages/cmd/export.go index f25a726e7..96d8817b5 100644 --- a/cli/packages/cmd/export.go +++ b/cli/packages/cmd/export.go @@ -29,58 +29,46 @@ var exportCmd = &cobra.Command{ DisableFlagsInUseLine: true, Example: "infisical export --env=prod --format=json > secrets.json", Args: cobra.NoArgs, - PreRun: toggleDebug, + PreRun: func(cmd *cobra.Command, args []string) { + toggleDebug(cmd, args) + util.RequireLogin() + util.RequireLocalWorkspaceFile() + }, Run: func(cmd *cobra.Command, args []string) { envName, err := cmd.Flags().GetString("env") if err != nil { - log.Errorln("Unable to parse the environment flag") - log.Debugln(err) - return + util.HandleError(err) } shouldExpandSecrets, err := cmd.Flags().GetBool("expand") if err != nil { - log.Errorln("Unable to parse the substitute flag") - log.Debugln(err) - return - } - - projectId, err := cmd.Flags().GetString("projectId") - if err != nil { - log.Errorln("Unable to parse the project id flag") - log.Debugln(err) - return + util.HandleError(err) } format, err := cmd.Flags().GetString("format") if err != nil { - log.Errorln("Unable to parse the format flag") - log.Debugln(err) - return + util.HandleError(err) } - envsFromApi, err := util.GetAllEnvironmentVariables(projectId, envName) + secrets, err := util.GetAllEnvironmentVariables(envName) 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 + util.HandleError(err, "Unable to fetch secrets") } var output string if shouldExpandSecrets { - substitutions := util.SubstituteSecrets(envsFromApi) + substitutions := util.SubstituteSecrets(secrets) output, err = formatEnvs(substitutions, format) if err != nil { - log.Errorln(err) - return + util.HandleError(err) } } else { - output, err = formatEnvs(envsFromApi, format) + output, err = formatEnvs(secrets, format) if err != nil { - log.Errorln(err) - return + util.HandleError(err) } } + fmt.Print(output) }, } @@ -88,7 +76,6 @@ var exportCmd = &cobra.Command{ func init() { rootCmd.AddCommand(exportCmd) exportCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from") - exportCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from") exportCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets") exportCmd.Flags().StringP("format", "f", "dotenv", "Set the format of the output file (dotenv, json, csv)") } diff --git a/cli/packages/cmd/init.go b/cli/packages/cmd/init.go index 2789f220d..a9a2d943d 100644 --- a/cli/packages/cmd/init.go +++ b/cli/packages/cmd/init.go @@ -5,10 +5,13 @@ package cmd import ( "encoding/json" + "fmt" "os" + "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/models" "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" @@ -21,21 +24,10 @@ var initCmd = &cobra.Command{ DisableFlagsInUseLine: true, Example: "infisical init", Args: cobra.ExactArgs(0), - PreRun: toggleDebug, + PreRun: func(cmd *cobra.Command, args []string) { + util.RequireLogin() + }, 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.WorkspaceConfigFileExistsInCurrentPath() { shouldOverride, err := shouldOverrideWorkspacePrompt() if err != nil { @@ -49,23 +41,22 @@ var initCmd = &cobra.Command{ } } - userCreds, err := util.GetUserCredsFromKeyRing(loggedInUserEmail) + userCreds, err := util.GetCurrentLoggedInUserDetails() if err != nil { - log.Infoln("Unable to get user creds from key ring") - log.Debug(err) - return + util.HandleError(err, "Unable to get your login details") } - workspaces, err := util.GetWorkSpacesFromAPI(userCreds) + httpClient := resty.New() + httpClient.SetAuthToken(userCreds.UserCredentials.JTWToken) + workspaceResponse, err := api.CallGetAllWorkSpacesUserBelongsTo(httpClient) 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 + util.HandleError(err, "Unable to pull projects that belong to you") } + workspaces := workspaceResponse.Workspaces 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 + message := fmt.Sprintf("You don't have any projects created in Infisical. You must first create a project at %s", util.INFISICAL_TOKEN_NAME) + util.PrintMessageAndExit(message) } var workspaceNames []string @@ -81,16 +72,12 @@ var initCmd = &cobra.Command{ index, _, err := prompt.Run() if err != nil { - log.Errorln("Unable to parse your response") - log.Debug(err) - return + util.HandleError(err) } 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 + util.HandleError(err) } }, } diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index 9c0c22930..923782e3f 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -11,6 +11,9 @@ import ( "fmt" "regexp" + "github.com/Infisical/infisical-merge/packages/api" + "github.com/Infisical/infisical-merge/packages/config" + "github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/srp" "github.com/Infisical/infisical-merge/packages/util" @@ -27,18 +30,15 @@ var loginCmd = &cobra.Command{ DisableFlagsInUseLine: true, PreRun: toggleDebug, Run: func(cmd *cobra.Command, args []string) { - hasUserLoggedInbefore, currentLoggedInUserEmail, err := util.IsUserLoggedIn() - + currentLoggedInUserDetails, err := util.GetCurrentLoggedInUserDetails() if err != nil { - log.Debugln("Unable to get current logged in user.", err) + util.HandleError(err) } - if hasUserLoggedInbefore { - shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserEmail) + if currentLoggedInUserDetails.IsUserLoggedIn { + shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserDetails.UserCredentials.Email) if err != nil { - log.Errorln("Unable to parse your answer") - log.Debug(err) - return + util.HandleError(err) } if !shouldOverride { @@ -48,14 +48,12 @@ var loginCmd = &cobra.Command{ email, password, err := askForLoginCredentials() if err != nil { - log.Errorln("Unable to parse email and password for authentication") - log.Debugln(err) - return + util.HandleError(err, "Unable to parse email and password for authentication") } userCredentials, err := getFreshUserCredentials(email, password) if err != nil { - log.Errorln("Unable to authenticate with the provided credentials, please try again") + log.Infoln("Unable to authenticate with the provided credentials, please try again") log.Debugln(err) return } @@ -63,24 +61,20 @@ var loginCmd = &cobra.Command{ 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) + util.HandleError(err) } IV, err := base64.StdEncoding.DecodeString(userCredentials.IV) if err != nil { - log.Errorln("Unable to decode the IV/Nonce") - log.Debugln(err) + util.HandleError(err) } paddedPassword := fmt.Sprintf("%032s", password) key := []byte(paddedPassword) - decryptedPrivateKey, err := util.DecryptSymmetric(key, encryptedPrivateKey, tag, IV) + decryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV) if err != nil || len(decryptedPrivateKey) == 0 { - log.Errorln("There was an issue decrypting your keys") - log.Debugln(err) - return + util.HandleError(err) } userCredentialsToBeStored := &models.UserCredentials{ @@ -100,9 +94,7 @@ var loginCmd = &cobra.Command{ err = util.WriteInitalConfig(userCredentialsToBeStored) if err != nil { - log.Errorln("Unable to write write to Infisical Config file. Please try again") - log.Debugln(err) - return + util.HandleError(err, "Unable to write write to Infisical Config file. Please try again") } log.Infoln("Nice! You are loggin as:", email) @@ -156,7 +148,7 @@ func askForLoginCredentials() (email string, password string, err error) { return userEmail, userPassword, nil } -func getFreshUserCredentials(email string, password string) (*models.LoginTwoResponse, error) { +func getFreshUserCredentials(email string, password string) (*api.LoginTwoResponse, error) { log.Debugln("getFreshUserCredentials:", "email", email, "password", password) httpClient := resty.New() httpClient.SetRetryCount(5) @@ -167,18 +159,18 @@ func getFreshUserCredentials(email string, password string) (*models.LoginTwoRes srpA := hex.EncodeToString(srpClient.ComputeA()) // ** Login one - loginOneRequest := models.LoginOneRequest{ + loginOneRequest := api.LoginOneRequest{ Email: email, ClientPublicKey: srpA, } - var loginOneResponseResult models.LoginOneResponse + var loginOneResponseResult api.LoginOneResponse loginOneResponse, err := httpClient. R(). SetBody(loginOneRequest). SetResult(&loginOneResponseResult). - Post(fmt.Sprintf("%v/v1/auth/login1", util.INFISICAL_URL)) + Post(fmt.Sprintf("%v/v1/auth/login1", config.INFISICAL_URL)) if err != nil { return nil, err @@ -204,17 +196,17 @@ func getFreshUserCredentials(email string, password string) (*models.LoginTwoRes srpM1 := srpClient.ComputeM1() - LoginTwoRequest := models.LoginTwoRequest{ + LoginTwoRequest := api.LoginTwoRequest{ Email: email, ClientProof: hex.EncodeToString(srpM1), } - var loginTwoResponseResult models.LoginTwoResponse + var loginTwoResponseResult api.LoginTwoResponse loginTwoResponse, err := httpClient. R(). SetBody(LoginTwoRequest). SetResult(&loginTwoResponseResult). - Post(fmt.Sprintf("%v/v1/auth/login2", util.INFISICAL_URL)) + Post(fmt.Sprintf("%v/v1/auth/login2", config.INFISICAL_URL)) if err != nil { return nil, err diff --git a/cli/packages/cmd/root.go b/cli/packages/cmd/root.go index f09f08800..5e6536e11 100644 --- a/cli/packages/cmd/root.go +++ b/cli/packages/cmd/root.go @@ -6,7 +6,7 @@ package cmd import ( "os" - "github.com/Infisical/infisical-merge/packages/util" + "github.com/Infisical/infisical-merge/packages/config" "github.com/spf13/cobra" ) @@ -30,7 +30,7 @@ func Execute() { 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://app.infisical.com/api", "Point the CLI to your own backend") + rootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", "https://app.infisical.com/api", "Point the CLI to your own backend") // rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { // } } diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go index 6e0133c8e..87b4cda4e 100644 --- a/cli/packages/cmd/run.go +++ b/cli/packages/cmd/run.go @@ -55,36 +55,26 @@ var runCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { envName, err := cmd.Flags().GetString("env") if err != nil { - log.Errorln("Unable to parse the environment flag") - log.Debugln(err) - return + util.HandleError(err, "Unable to parse flag") + } + + if !util.IsSecretEnvironmentValid(envName) { + util.PrintMessageAndExit("Invalid environment name passed. Environment names can only be prod, dev, test or staging") } secretOverriding, err := cmd.Flags().GetBool("secret-overriding") if err != nil { - log.Errorln("Unable to parse the secret-overriding flag") - log.Debugln(err) - return + util.HandleError(err, "Unable to parse flag") } shouldExpandSecrets, err := cmd.Flags().GetBool("expand") if err != nil { - log.Errorln("Unable to parse the substitute flag") - log.Debugln(err) - return + util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + secrets, err := util.GetAllEnvironmentVariables(envName) if err != nil { - log.Errorln("Unable to parse the project id flag") - log.Debugln(err) - return - } - - secrets, err := util.GetAllEnvironmentVariables(projectId, envName) - if err != nil { - log.Debugln(err) - return + util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid") } if shouldExpandSecrets { @@ -97,29 +87,26 @@ var runCmd = &cobra.Command{ if cmd.Flags().Changed("command") { command := cmd.Flag("command").Value.String() + err = executeMultipleCommandWithEnvs(command, secrets) if err != nil { - log.Errorf("Something went wrong when executing your command [error=%s]", err) - return + util.HandleError(err, "Unable to execute your chained command") } + } else { err = executeSingleCommandWithEnvs(args, secrets) if err != nil { - log.Errorf("Something went wrong when executing your command [error=%s]", err) - return + util.HandleError(err, "Unable to execute your single command") } - return } - }, } func init() { rootCmd.AddCommand(runCmd) runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from") - runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from") runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets") - runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets with the same name over shared secrets") + runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets") runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")") } @@ -130,7 +117,7 @@ func executeSingleCommandWithEnvs(args []string, secrets []models.SingleEnvironm numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(secrets)) log.Infof("\x1b[%dm%s\x1b[0m", 32, numberOfSecretsInjected) log.Debugf("executing command: %s %s \n", command, strings.Join(argsForCommand, " ")) - log.Debugln("Secrets injected:", secrets) + log.Debugf("Secrets injected: %v", secrets) cmd := exec.Command(command, argsForCommand...) cmd.Stdin = os.Stdin @@ -158,7 +145,7 @@ func executeMultipleCommandWithEnvs(fullCommand string, secrets []models.SingleE numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(secrets)) log.Infof("\x1b[%dm%s\x1b[0m", 32, numberOfSecretsInjected) log.Debugf("executing command: %s %s %s \n", shell[0], shell[1], fullCommand) - log.Debugln("Secrets injected:", secrets) + log.Debugf("Secrets injected: %v", secrets) return execCmd(cmd) } diff --git a/cli/packages/cmd/secrets.go b/cli/packages/cmd/secrets.go index 5e6c4ca16..810da49c4 100644 --- a/cli/packages/cmd/secrets.go +++ b/cli/packages/cmd/secrets.go @@ -11,7 +11,8 @@ import ( "crypto/sha256" - "github.com/Infisical/infisical-merge/packages/http" + "github.com/Infisical/infisical-merge/packages/api" + "github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/util" "github.com/Infisical/infisical-merge/packages/visualize" @@ -30,35 +31,23 @@ var secretsCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { environmentName, err := cmd.Flags().GetString("env") if err != nil { - log.Errorln("Unable to parse the environment name flag") - log.Debugln(err) - return + util.HandleError(err) } shouldExpandSecrets, err := cmd.Flags().GetBool("expand") if err != nil { - log.Errorln("Unable to parse the substitute flag") - log.Debugln(err) - return + util.HandleError(err) } - workspaceFileExists := util.WorkspaceConfigFileExistsInCurrentPath() - if !workspaceFileExists { - log.Error("You have not yet connected to an Infisical Project. Please run [infisical init]") - return + secrets, err := util.GetAllEnvironmentVariables(environmentName) + if err != nil { + util.HandleError(err) } - secrets, err := util.GetAllEnvironmentVariables("", environmentName) - if shouldExpandSecrets { secrets = util.SubstituteSecrets(secrets) } - if err != nil { - log.Debugln(err) - return - } - visualize.PrintAllSecretDetails(secrets) }, } @@ -81,85 +70,50 @@ var secretsSetCmd = &cobra.Command{ PreRun: toggleDebug, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - // secretType, err := cmd.Flags().GetString("type") - // if err != nil { - // log.Errorln("Unable to parse the secret type flag") - // log.Debugln(err) - // return - // } - - // if !util.IsSecretTypeValid(secretType) { - // log.Errorf("secret type can only be `personal` or `shared`. You have entered [%v]", secretType) - // return - // } - environmentName, err := cmd.Flags().GetString("env") if err != nil { - log.Errorln("Unable to parse the environment name flag") - log.Debugln(err) - return + util.HandleError(err, "Unable to parse flag") } if !util.IsSecretEnvironmentValid(environmentName) { - log.Errorln("You have entered a invalid environment name. Environment names can only be prod, dev, test or staging") - return - } - - workspaceFileExists := util.WorkspaceConfigFileExistsInCurrentPath() - if !workspaceFileExists { - log.Error("You have not yet connected to an Infisical Project. Please run [infisical init]") - return + util.PrintMessageAndExit("You have entered a invalid environment name", "Environment names can only be prod, dev, test or staging") } workspaceFile, err := util.GetWorkSpaceFromFile() if err != nil { - log.Error(err) - return + util.HandleError(err, "Unable to get your local config details") } loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails() if err != nil { - log.Error(err) - return - } - - if !loggedInUserDetails.IsUserLoggedIn { - log.Error("You are not logged in yet. Please run [infisical login] then try again") - return - } - - if loggedInUserDetails.IsUserLoggedIn && loggedInUserDetails.LoginExpired { - log.Error("Your login has expired. Please run [infisical login] then try again") - return + util.HandleError(err, "Unable to authenticate") } httpClient := resty.New(). SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken). SetHeader("Accept", "application/json") - request := models.GetEncryptedWorkspaceKeyRequest{ + request := api.GetEncryptedWorkspaceKeyRequest{ WorkspaceId: workspaceFile.WorkspaceId, } - workspaceKeyResponse, err := http.CallGetEncryptedWorkspaceKey(httpClient, request) + workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request) if err != nil { - log.Errorf("unable to get your encrypted workspace key. [err=%v]", err) - return + util.HandleError(err, "unable to get your encrypted workspace key") } - encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.LatestKey.EncryptedKey) - encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.LatestKey.Sender.PublicKey) - encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.LatestKey.Nonce) + encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey) + encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey) + encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce) currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey) // decrypt workspace key - plainTextEncryptionKey := util.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey) + plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey) // pull current secrets - secrets, err := util.GetAllEnvironmentVariables("", environmentName) + secrets, err := util.GetAllEnvironmentVariables(environmentName) if err != nil { - log.Error("unable to retrieve secrets. Run with -d to see full logs") - log.Debug(err) + util.HandleError(err, "unable to retrieve secrets") } type SecretSetOperation struct { @@ -168,8 +122,8 @@ var secretsSetCmd = &cobra.Command{ SecretOperation string } - secretsToCreate := []models.Secret{} - secretsToModify := []models.Secret{} + secretsToCreate := []api.Secret{} + secretsToModify := []api.Secret{} secretOperations := []SecretSetOperation{} secretByKey := getSecretsByKeys(secrets) @@ -177,13 +131,11 @@ var secretsSetCmd = &cobra.Command{ for _, arg := range args { splitKeyValueFromArg := strings.SplitN(arg, "=", 2) if splitKeyValueFromArg[0] == "" || splitKeyValueFromArg[1] == "" { - log.Error("ensure that each secret has a none empty key and value. Modify the input and try again") - return + util.PrintMessageAndExit("ensure that each secret has a none empty key and value. Modify the input and try again") } if unicode.IsNumber(rune(splitKeyValueFromArg[0][0])) { - log.Error("keys of secrets cannot start with a number. Modify the key name(s) and try again") - return + util.PrintMessageAndExit("keys of secrets cannot start with a number. Modify the key name(s) and try again") } // Key and value from argument @@ -191,20 +143,20 @@ var secretsSetCmd = &cobra.Command{ value := splitKeyValueFromArg[1] hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key))) - encryptedKey, err := util.EncryptSymmetric([]byte(key), []byte(plainTextEncryptionKey)) + encryptedKey, err := crypto.EncryptSymmetric([]byte(key), []byte(plainTextEncryptionKey)) if err != nil { - log.Errorf("unable to encrypt your secrets [err=%v]", err) + util.HandleError(err, "unable to encrypt your secrets") } hashedValue := fmt.Sprintf("%x", sha256.Sum256([]byte(value))) - encryptedValue, err := util.EncryptSymmetric([]byte(value), []byte(plainTextEncryptionKey)) + encryptedValue, err := crypto.EncryptSymmetric([]byte(value), []byte(plainTextEncryptionKey)) if err != nil { - log.Errorf("unable to encrypt your secrets [err=%v]", err) + util.HandleError(err, "unable to encrypt your secrets") } if existingSecret, ok := secretByKey[key]; ok { // case: secret exists in project so it needs to be modified - encryptedSecretDetails := models.Secret{ + encryptedSecretDetails := api.Secret{ ID: existingSecret.ID, SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText), SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce), @@ -231,7 +183,7 @@ var secretsSetCmd = &cobra.Command{ } else { // case: secret doesn't exist in project so it needs to be created - encryptedSecretDetails := models.Secret{ + encryptedSecretDetails := api.Secret{ SecretKeyCiphertext: base64.StdEncoding.EncodeToString(encryptedKey.CipherText), SecretKeyIV: base64.StdEncoding.EncodeToString(encryptedKey.Nonce), SecretKeyTag: base64.StdEncoding.EncodeToString(encryptedKey.AuthTag), @@ -252,29 +204,29 @@ var secretsSetCmd = &cobra.Command{ } if len(secretsToCreate) > 0 { - batchCreateRequest := models.BatchCreateSecretsByWorkspaceAndEnvRequest{ + batchCreateRequest := api.BatchCreateSecretsByWorkspaceAndEnvRequest{ WorkspaceId: workspaceFile.WorkspaceId, EnvironmentName: environmentName, Secrets: secretsToCreate, } - err = http.CallBatchCreateSecretsByWorkspaceAndEnv(httpClient, batchCreateRequest) + err = api.CallBatchCreateSecretsByWorkspaceAndEnv(httpClient, batchCreateRequest) if err != nil { - log.Errorf("Unable to process new secret creations because %v", err) + util.HandleError(err, "Unable to process new secret creations") return } } if len(secretsToModify) > 0 { - batchModifyRequest := models.BatchModifySecretsByWorkspaceAndEnvRequest{ + batchModifyRequest := api.BatchModifySecretsByWorkspaceAndEnvRequest{ WorkspaceId: workspaceFile.WorkspaceId, EnvironmentName: environmentName, Secrets: secretsToModify, } - err = http.CallBatchModifySecretsByWorkspaceAndEnv(httpClient, batchModifyRequest) + err = api.CallBatchModifySecretsByWorkspaceAndEnv(httpClient, batchModifyRequest) if err != nil { - log.Errorf("Unable to process the modifications to your secrets because %v", err) + util.HandleError(err, "Unable to process the modifications to your secrets") return } } @@ -307,36 +259,17 @@ var secretsDeleteCmd = &cobra.Command{ loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails() if err != nil { - log.Error(err) - return - } - - if !loggedInUserDetails.IsUserLoggedIn { - log.Error("You are not logged in yet. Please run [infisical login] then try again") - return - } - - if loggedInUserDetails.IsUserLoggedIn && loggedInUserDetails.LoginExpired { - log.Error("Your login has expired. Please run [infisical login] then try again") - return - } - - workspaceFileExists := util.WorkspaceConfigFileExistsInCurrentPath() - if !workspaceFileExists { - log.Error("You have not yet connected to an Infisical Project. Please run [infisical init]") - return + util.HandleError(err, "Unable to authenticate") } workspaceFile, err := util.GetWorkSpaceFromFile() if err != nil { - log.Error(err) - return + util.HandleError(err, "Unable to get local project details") } - secrets, err := util.GetAllEnvironmentVariables("", environmentName) + secrets, err := util.GetAllEnvironmentVariables(environmentName) if err != nil { - log.Error("Unable to retrieve secrets. Run with -d to see full logs") - log.Debug(err) + util.HandleError(err, "Unable to fetch secrets") } secretByKey := getSecretsByKeys(secrets) @@ -352,11 +285,11 @@ var secretsDeleteCmd = &cobra.Command{ } if len(invalidSecretNamesThatDoNotExist) != 0 { - log.Errorf("secret name(s) [%v] does not exist in your project. To see which secrets exist run [infisical secrets]", strings.Join(invalidSecretNamesThatDoNotExist, ", ")) - return + message := fmt.Sprintf("secret name(s) [%v] does not exist in your project. To see which secrets exist run [infisical secrets]", strings.Join(invalidSecretNamesThatDoNotExist, ", ")) + util.PrintMessageAndExit(message) } - request := models.BatchDeleteSecretsBySecretIdsRequest{ + request := api.BatchDeleteSecretsBySecretIdsRequest{ WorkspaceId: workspaceFile.WorkspaceId, EnvironmentName: environmentName, SecretIds: validSecretIdsToDelete, @@ -366,45 +299,43 @@ var secretsDeleteCmd = &cobra.Command{ SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken). SetHeader("Accept", "application/json") - err = http.CallBatchDeleteSecretsByWorkspaceAndEnv(httpClient, request) + err = api.CallBatchDeleteSecretsByWorkspaceAndEnv(httpClient, request) if err != nil { - log.Errorf("Unable to complete your request because %v", err) - return + util.HandleError(err, "Unable to complete your batch delete request") } - log.Infof("secret name(s) [%v] have been deleted from your project", strings.Join(args, ", ")) + fmt.Printf("secret name(s) [%v] have been deleted from your project \n", strings.Join(args, ", ")) }, } func init() { secretsCmd.AddCommand(secretsGetCmd) - // secretsSetCmd.Flags().String("type", "shared", "Used to set the type for secrets") secretsCmd.AddCommand(secretsSetCmd) secretsCmd.AddCommand(secretsDeleteCmd) secretsCmd.PersistentFlags().String("env", "dev", "Used to define the environment name on which actions should be taken on") secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets") + secretsCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { + util.RequireLogin() + util.RequireLocalWorkspaceFile() + } rootCmd.AddCommand(secretsCmd) } func getSecretsByNames(cmd *cobra.Command, args []string) { environmentName, err := cmd.Flags().GetString("env") if err != nil { - log.Errorln("Unable to parse the environment name flag") - log.Debugln(err) - return + util.HandleError(err, "Unable to parse flag") } workspaceFileExists := util.WorkspaceConfigFileExistsInCurrentPath() if !workspaceFileExists { - log.Error("You have not yet connected to an Infisical Project. Please run [infisical init]") - return + util.HandleError(err, "Unable to parse flag") } - secrets, err := util.GetAllEnvironmentVariables("", environmentName) + secrets, err := util.GetAllEnvironmentVariables(environmentName) if err != nil { - log.Error("Unable to retrieve secrets. Run with -d to see full logs") - log.Debug(err) + util.HandleError(err, "To fetch all secrets") } requestedSecrets := []models.SingleEnvironmentVariable{} diff --git a/cli/packages/config/config.go b/cli/packages/config/config.go new file mode 100644 index 000000000..5ceb695e6 --- /dev/null +++ b/cli/packages/config/config.go @@ -0,0 +1,3 @@ +package config + +var INFISICAL_URL = "http://localhost:8080/api" diff --git a/cli/packages/util/crypto.go b/cli/packages/crypto/crypto.go similarity index 99% rename from cli/packages/util/crypto.go rename to cli/packages/crypto/crypto.go index 23e117f4b..f9589347a 100644 --- a/cli/packages/util/crypto.go +++ b/cli/packages/crypto/crypto.go @@ -1,4 +1,4 @@ -package util +package crypto import ( "crypto/aes" diff --git a/cli/packages/http/api.go b/cli/packages/http/api.go deleted file mode 100644 index 01b8a8a40..000000000 --- a/cli/packages/http/api.go +++ /dev/null @@ -1,102 +0,0 @@ -package http - -import ( - "fmt" - - "github.com/Infisical/infisical-merge/packages/models" - "github.com/Infisical/infisical-merge/packages/util" - "github.com/go-resty/resty/v2" -) - -func CallBatchModifySecretsByWorkspaceAndEnv(httpClient *resty.Client, request models.BatchModifySecretsByWorkspaceAndEnvRequest) error { - endpoint := fmt.Sprintf("%v/v2/secret/batch-modify/workspace/%v/environment/%v", util.INFISICAL_URL, request.WorkspaceId, request.EnvironmentName) - response, err := httpClient. - R(). - SetBody(request). - Patch(endpoint) - - if err != nil { - return fmt.Errorf("CallBatchModifySecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err) - } - - if response.StatusCode() > 299 { - return fmt.Errorf("CallBatchModifySecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response) - } - - return nil -} - -func CallBatchCreateSecretsByWorkspaceAndEnv(httpClient *resty.Client, request models.BatchCreateSecretsByWorkspaceAndEnvRequest) error { - endpoint := fmt.Sprintf("%v/v2/secret/batch-create/workspace/%v/environment/%v", util.INFISICAL_URL, request.WorkspaceId, request.EnvironmentName) - response, err := httpClient. - R(). - SetBody(request). - Post(endpoint) - - if err != nil { - return fmt.Errorf("CallBatchCreateSecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err) - } - - if response.StatusCode() > 299 { - return fmt.Errorf("CallBatchCreateSecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response) - } - - return nil -} - -func CallBatchDeleteSecretsByWorkspaceAndEnv(httpClient *resty.Client, request models.BatchDeleteSecretsBySecretIdsRequest) error { - endpoint := fmt.Sprintf("%v/v2/secret/batch/workspace/%v/environment/%v", util.INFISICAL_URL, request.WorkspaceId, request.EnvironmentName) - response, err := httpClient. - R(). - SetBody(request). - Delete(endpoint) - - if err != nil { - return fmt.Errorf("CallBatchDeleteSecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err) - } - - if response.StatusCode() > 299 { - return fmt.Errorf("CallBatchDeleteSecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response) - } - - return nil -} - -func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request models.GetEncryptedWorkspaceKeyRequest) (models.GetEncryptedWorkspaceKeyResponse, error) { - endpoint := fmt.Sprintf("%v/v1/key/%v/latest", util.INFISICAL_URL, request.WorkspaceId) - var result models.GetEncryptedWorkspaceKeyResponse - response, err := httpClient. - R(). - SetResult(&result). - Get(endpoint) - - if err != nil { - return models.GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err) - } - - if response.StatusCode() > 299 { - return models.GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response: [response=%s]", response) - } - - return result, nil -} - -func CallGetEncryptedSecretsByWorkspaceIdAndEnv(httpClient resty.Client, request models.GetSecretsByWorkspaceIdAndEnvironmentRequest) (models.PullSecretsResponse, error) { - var pullSecretsRequestResponse models.PullSecretsResponse - response, err := httpClient. - R(). - SetQueryParam("environment", request.EnvironmentName). - SetQueryParam("channel", "cli"). - SetResult(&pullSecretsRequestResponse). - Get(fmt.Sprintf("%v/v1/secret/%v", util.INFISICAL_URL, request.WorkspaceId)) - - if err != nil { - return models.PullSecretsResponse{}, fmt.Errorf("CallGetEncryptedSecretsByWorkspaceIdAndEnv: Unable to complete api request [err=%s]", err) - } - - if response.StatusCode() > 299 { - return models.PullSecretsResponse{}, fmt.Errorf("CallGetEncryptedSecretsByWorkspaceIdAndEnv: Unsuccessful response: [response=%s]", response) - } - - return pullSecretsRequestResponse, nil -} diff --git a/cli/packages/models/cli.go b/cli/packages/models/cli.go index 70484de81..7bc31cab2 100644 --- a/cli/packages/models/cli.go +++ b/cli/packages/models/cli.go @@ -21,6 +21,14 @@ type SingleEnvironmentVariable struct { ID string `json:"_id"` } +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"` +} + type WorkspaceConfigFile struct { WorkspaceId string `json:"workspaceId"` } diff --git a/cli/packages/util/common.go b/cli/packages/util/common.go index 44f14a12b..2d420ac6f 100644 --- a/cli/packages/util/common.go +++ b/cli/packages/util/common.go @@ -5,17 +5,6 @@ import ( "os" ) -const ( - CONFIG_FILE_NAME = "infisical-config.json" - CONFIG_FOLDER_NAME = ".infisical" - INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json" - INFISICAL_TOKEN_NAME = "INFISICAL_TOKEN" - SECRET_TYPE_PERSONAL = "personal" - SECRET_TYPE_SHARED = "shared" -) - -var INFISICAL_URL = "https://app.infisical.com/api" - func GetHomeDir() (string, error) { directory, err := os.UserHomeDir() return directory, err @@ -25,7 +14,7 @@ func GetHomeDir() (string, error) { 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 fmt.Errorf("unable to wrote to file [err=%v]", err) } return nil diff --git a/cli/packages/util/constants.go b/cli/packages/util/constants.go new file mode 100644 index 000000000..fc7f5d581 --- /dev/null +++ b/cli/packages/util/constants.go @@ -0,0 +1,13 @@ +package util + +const ( + CONFIG_FILE_NAME = "infisical-config.json" + CONFIG_FOLDER_NAME = ".infisical" + INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json" + INFISICAL_TOKEN_NAME = "INFISICAL_TOKEN" + SECRET_TYPE_PERSONAL = "personal" + SECRET_TYPE_SHARED = "shared" + KEYRING_SERVICE_NAME = "infisical" + PERSONAL_SECRET_TYPE_NAME = "personal" + SHARED_SECRET_TYPE_NAME = "shared" +) diff --git a/cli/packages/util/credentials.go b/cli/packages/util/credentials.go index bd0e67deb..6410002c0 100644 --- a/cli/packages/util/credentials.go +++ b/cli/packages/util/credentials.go @@ -5,20 +5,17 @@ import ( "fmt" "github.com/99designs/keyring" + "github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/models" "github.com/go-resty/resty/v2" - log "github.com/sirupsen/logrus" ) -const SERVICE_NAME = "infisical" - type LoggedInUserDetails struct { IsUserLoggedIn bool LoginExpired bool UserCredentials models.UserCredentials } -// 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 { @@ -69,46 +66,6 @@ func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentia return userCredentials, err } -func IsUserLoggedIn() (hasUserLoggedIn bool, theUsersEmail string, err error) { - if ConfigFileExists() { - configFile, err := GetConfigFile() - if err != nil { - return false, "", fmt.Errorf("IsUserLoggedIn: unable to get logged in user from config file [err=%s]", err) - } - - if configFile.LoggedInUserEmail == "" { - return false, "", nil - } - - 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/v1/auth/checkAuth", INFISICAL_URL)) - - if err != nil { - return false, "", err - } - - if response.StatusCode() > 299 { - log.Infoln("Login expired, please login again.") - return false, "", fmt.Errorf("GetUserCredsFromKeyRing: Login expired, please login again.") - } - - return true, configFile.LoggedInUserEmail, nil - } else { - return false, "", nil - } -} - func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) { if ConfigFileExists() { configFile, err := GetConfigFile() @@ -132,7 +89,7 @@ func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) { response, err := httpClient. R(). - Post(fmt.Sprintf("%v/v1/auth/checkAuth", INFISICAL_URL)) + Post(fmt.Sprintf("%v/v1/auth/checkAuth", config.INFISICAL_URL)) if err != nil { return LoggedInUserDetails{}, err diff --git a/cli/packages/util/errors.go b/cli/packages/util/errors.go new file mode 100644 index 000000000..1761d6f7b --- /dev/null +++ b/cli/packages/util/errors.go @@ -0,0 +1,38 @@ +package util + +import ( + "fmt" + "os" + + "github.com/fatih/color" +) + +func HandleError(err error, messages ...string) { + PrintErrorAndExit(1, err, messages...) +} + +func PrintErrorAndExit(exitCode int, err error, messages ...string) { + printError(err) + + if len(messages) > 0 { + for _, message := range messages { + fmt.Println(message) + } + } + + os.Exit(exitCode) +} + +func PrintMessageAndExit(messages ...string) { + if len(messages) > 0 { + for _, message := range messages { + fmt.Println(message) + } + } + + os.Exit(1) +} + +func printError(e error) { + color.Red("Hmm, we ran into an error: %v", e) +} diff --git a/cli/packages/util/helper.go b/cli/packages/util/helper.go new file mode 100644 index 000000000..a6e7e35fc --- /dev/null +++ b/cli/packages/util/helper.go @@ -0,0 +1,100 @@ +package util + +import ( + "encoding/base64" + "fmt" + "os" +) + +type DecodedSymmetricEncryptionDetails = struct { + Cipher []byte + IV []byte + Tag []byte + Key []byte +} + +func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) { + cipherx, err := base64.StdEncoding.DecodeString(cipher) + if err != nil { + return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode cipher text [err=%v]", err) + } + + keyx, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode key [err=%v]", err) + } + + IVx, err := base64.StdEncoding.DecodeString(IV) + if err != nil { + return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode IV [err=%v]", err) + } + + tagx, err := base64.StdEncoding.DecodeString(tag) + if err != nil { + return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode tag [err=%v]", err) + } + + return DecodedSymmetricEncryptionDetails{ + Key: keyx, + Cipher: cipherx, + IV: IVx, + Tag: tagx, + }, nil +} + +func IsSecretEnvironmentValid(env string) bool { + if env == "prod" || env == "dev" || env == "test" || env == "staging" { + return true + } + return false +} + +func IsSecretTypeValid(s string) bool { + if s == "personal" || s == "shared" { + return true + } + return false +} + +func RequireLogin() { + currentUserDetails, err := GetCurrentLoggedInUserDetails() + + if err != nil { + HandleError(err, "unable to retrieve your login details") + } + + if !currentUserDetails.IsUserLoggedIn { + PrintMessageAndExit("You must be logged in to run this command. To login, run [infisical login]") + } + + if currentUserDetails.LoginExpired { + PrintMessageAndExit("Your login expired, please login in again. To login, run [infisical login]") + } + + if currentUserDetails.UserCredentials.Email == "" && currentUserDetails.UserCredentials.JTWToken == "" && currentUserDetails.UserCredentials.PrivateKey == "" { + PrintMessageAndExit("One or more of your login details is empty. Please try logging in again via by running [infisical login]") + } +} + +func RequireServiceToken() { + serviceToken := os.Getenv(INFISICAL_TOKEN_NAME) + if serviceToken == "" { + PrintMessageAndExit("No service token is found in your terminal") + } +} + +func RequireLocalWorkspaceFile() { + workspaceFileExists := WorkspaceConfigFileExistsInCurrentPath() + if !workspaceFileExists { + PrintMessageAndExit("It looks you have not yet connected this project to Infisical", "To do so, run [infisical init] then run your command again") + } + + workspaceFile, err := GetWorkSpaceFromFile() + if err != nil { + HandleError(err, "Unable to read your project configuration, please try initializing this project again.", "Run [infisical init]") + } + + if workspaceFile.WorkspaceId == "" { + PrintMessageAndExit("Your project id is missing in your local config file. Please add it or run again [infisical init]") + } +} diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index 66d2b56e3..9c5c12d09 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -2,284 +2,127 @@ package util import ( "encoding/base64" - "errors" "fmt" "os" "regexp" "strings" + "github.com/Infisical/infisical-merge/packages/api" + "github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/models" - "github.com/go-resty/resty/v2" log "github.com/sirupsen/logrus" + + "github.com/go-resty/resty/v2" ) -const PERSONAL_SECRET_TYPE_NAME = "personal" -const SHARED_SECRET_TYPE_NAME = "shared" - -func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string, workspace models.WorkspaceConfigFile, userCreds models.UserCredentials) (listOfSecrets []models.SingleEnvironmentVariable, err error) { - var pullSecretsRequestResponse models.PullSecretsResponse - response, err := httpClient. - R(). - SetQueryParam("environment", envName). - SetQueryParam("channel", "cli"). - SetResult(&pullSecretsRequestResponse). - Get(fmt.Sprintf("%v/v1/secret/%v", INFISICAL_URL, workspace.WorkspaceId)) // need to change workspace id - - if err != nil { - return nil, err +func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, error) { + serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) + if len(serviceTokenParts) < 4 { + return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again") } - if response.StatusCode() > 299 { - return nil, fmt.Errorf(response.Status()) - } + serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2]) - // 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 := DecryptAsymmetric(workspaceKey, nonce, senderPublicKey, 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), - Type: string(secret.Type), - ID: secret.ID, - } - - listOfEnv = append(listOfEnv, env) - } - - return listOfEnv, nil -} - -func GetSecretsFromAPIUsingCurrentLoggedInUser(envName string, userCreds models.UserCredentials) ([]models.SingleEnvironmentVariable, error) { - log.Debugln("GetSecretsFromAPIUsingCurrentLoggedInUser", "envName", envName, "userCreds", userCreds) - // check if user has configured a workspace - workspaces, err := GetAllWorkSpaceConfigsStartingFromCurrentPath() - if err != nil { - return nil, fmt.Errorf("Unable to read workspace file(s):", err) - } - - // create http client - httpClient := resty.New(). - SetAuthToken(userCreds.JTWToken). + httpClient := resty.New() + httpClient.SetAuthToken(serviceToken). SetHeader("Accept", "application/json") - secrets := []models.SingleEnvironmentVariable{} - for _, workspace := range workspaces { - secretsFromAPI, err := getSecretsByWorkspaceIdAndEnvName(*httpClient, envName, workspace, userCreds) - if err != nil { - return nil, fmt.Errorf("GetSecretsFromAPIUsingCurrentLoggedInUser: Unable to get secrets by workspace id and env name") - } - - secrets = append(secrets, secretsFromAPI...) + serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient) + if err != nil { + return nil, fmt.Errorf("unable to get service token details. [err=%v]", err) } - return secrets, nil + encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{ + WorkspaceId: serviceTokenDetails.Workspace, + EnvironmentName: serviceTokenDetails.Environment, + }) + + if err != nil { + return nil, err + } + + decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag) + if err != nil { + return nil, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err) + } + + plainTextWorkspaceKey, err := crypto.DecryptSymmetric(decodedSymmetricEncryptionDetails.Key, decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV) + if err != nil { + return nil, fmt.Errorf("unable to decrypt the required workspace key") + } + + plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets) + if err != nil { + return nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err) + } + + return plainTextSecrets, nil } -func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, projectId string) ([]models.SingleEnvironmentVariable, error) { - if infisicalToken == "" || projectId == "" || envName == "" { - return nil, errors.New("infisical token, project id and or environment name cannot be empty") - } - splitToken := strings.Split(infisicalToken, ",") - JTWToken := splitToken[0] - temPrivateKey := splitToken[1] - - // create http client - httpClient := resty.New(). - SetAuthToken(JTWToken). +func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string) ([]models.SingleEnvironmentVariable, error) { + httpClient := resty.New() + httpClient.SetAuthToken(JTWToken). SetHeader("Accept", "application/json") - var pullSecretsByInfisicalTokenResponse models.PullSecretsByInfisicalTokenResponse - response, err := httpClient. - R(). - SetQueryParam("environment", envName). - SetQueryParam("channel", "cli"). - SetResult(&pullSecretsByInfisicalTokenResponse). - Get(fmt.Sprintf("%v/v1/secret/%v/service-token", INFISICAL_URL, projectId)) + request := api.GetEncryptedWorkspaceKeyRequest{ + WorkspaceId: workspaceId, + } + + workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request) + if err != nil { + return nil, fmt.Errorf("unable to get your encrypted workspace key. [err=%v]", err) + } + + encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey) + encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey) + encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce) + currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(receiversPrivateKey) + plainTextWorkspaceKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey) + + encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{ + WorkspaceId: workspaceId, + EnvironmentName: environmentName, + }) if err != nil { return nil, err } - if response.StatusCode() > 299 { - return nil, fmt.Errorf(response.Status()) - } - - // Get workspace key - workspaceKey, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.EncryptedKey) + plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", 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)) - workspaceKeyInBytes := DecryptAsymmetric(workspaceKey, nonce, senderPublicKey, 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), - Type: string(secret.Type), - ID: secret.ID, - } - - listOfEnv = append(listOfEnv, env) - } - - return listOfEnv, nil + return plainTextSecrets, nil } -func GetAllEnvironmentVariables(projectId string, envName string) ([]models.SingleEnvironmentVariable, error) { +func GetAllEnvironmentVariables(envName string) ([]models.SingleEnvironmentVariable, error) { infisicalToken := os.Getenv(INFISICAL_TOKEN_NAME) if infisicalToken == "" { - hasUserLoggedInbefore, loggedInUserEmail, err := IsUserLoggedIn() + RequireLocalWorkspaceFile() + RequireLogin() + log.Debug("Trying to fetch secrets using logged in details") + + loggedInUserDetails, err := GetCurrentLoggedInUserDetails() if err != nil { - log.Info("Unexpected issue occurred while checking login status. To see more details, add flag --debug") - log.Debugln(err) return nil, err } - if !hasUserLoggedInbefore { - log.Infoln("No logged in user. To login, please run command [infisical login]") - return nil, fmt.Errorf("user not logged in") - } - - userCreds, err := GetUserCredsFromKeyRing(loggedInUserEmail) + workspaceFile, err := GetWorkSpaceFromFile() if err != nil { - log.Infoln("Unable to get user creds from key ring") - log.Debug(err) return nil, err } - // TODO: Should be based on flag. I.e only get all workspaces if desired, otherwise only get the one in the current root of project - workspaceConfigs, err := GetAllWorkSpaceConfigsStartingFromCurrentPath() - if err != nil { - return nil, fmt.Errorf("unable to check if you have a %s file in your current directory", INFISICAL_WORKSPACE_CONFIG_FILE_NAME) - } - - if len(workspaceConfigs) == 0 { - log.Infoln("Your local project is not connected to a Infisical project yet. Run command [infisical init]") - return nil, fmt.Errorf("project not initialized") - } - - envsFromApi, err := GetSecretsFromAPIUsingCurrentLoggedInUser(envName, 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 nil, err - } - - return envsFromApi, nil + secrets, err := GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, envName) + return secrets, err } else { - envsFromApi, err := GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, 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 nil, err - } - - return envsFromApi, nil + log.Debug("Trying to fetch secrets using service token") + return GetPlainTextSecretsViaServiceToken(infisicalToken) } } -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/v1/workspace", INFISICAL_URL)) - - 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 -} - func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string { if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found { return value @@ -351,6 +194,8 @@ func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.Sing return expandedSecrets } +// + // if two secrets with the same name are found, the one that has type `personal` will be in the returned list func OverrideWithPersonalSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable { personalSecret := make(map[string]models.SingleEnvironmentVariable) @@ -359,46 +204,80 @@ func OverrideWithPersonalSecrets(secrets []models.SingleEnvironmentVariable) []m for _, secret := range secrets { if secret.Type == PERSONAL_SECRET_TYPE_NAME { - personalSecret[secret.Key] = models.SingleEnvironmentVariable{ - Key: secret.Key, - Value: secret.Value, - Type: secret.Type, - } + personalSecret[secret.Key] = secret } if secret.Type == SHARED_SECRET_TYPE_NAME { - sharedSecret[secret.Key] = models.SingleEnvironmentVariable{ - Key: secret.Key, - Value: secret.Value, - Type: secret.Type, - } + sharedSecret[secret.Key] = secret } } - for _, secret := range secrets { + for _, secret := range sharedSecret { personalValue, personalExists := personalSecret[secret.Key] - sharedValue, sharedExists := sharedSecret[secret.Key] - - if personalExists && sharedExists || personalExists && !sharedExists { + if personalExists { secretsToReturn = append(secretsToReturn, personalValue) } else { - secretsToReturn = append(secretsToReturn, sharedValue) + secretsToReturn = append(secretsToReturn, secret) } } return secretsToReturn } -func IsSecretEnvironmentValid(env string) bool { - if env == "prod" || env == "dev" || env == "test" || env == "staging" { - return true - } - return false -} +func GetPlainTextSecrets(key []byte, encryptedSecrets api.GetEncryptedSecretsV2Response) ([]models.SingleEnvironmentVariable, error) { + plainTextSecrets := []models.SingleEnvironmentVariable{} + for _, secret := range encryptedSecrets { + // Decrypt key + key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV) + if err != nil { + return nil, fmt.Errorf("unable to decode secret IV for secret key") + } -func IsSecretTypeValid(s string) bool { - if s == "personal" || s == "shared" { - return true + key_tag, err := base64.StdEncoding.DecodeString(secret.SecretKeyTag) + if err != nil { + return nil, fmt.Errorf("unable to decode secret authentication tag for secret key") + } + + key_ciphertext, err := base64.StdEncoding.DecodeString(secret.SecretKeyCiphertext) + if err != nil { + return nil, fmt.Errorf("unable to decode secret cipher text for secret key") + } + + plainTextKey, err := crypto.DecryptSymmetric(key, key_ciphertext, key_tag, key_iv) + if err != nil { + return nil, fmt.Errorf("unable to symmetrically decrypt secret key") + } + + // Decrypt value + value_iv, err := base64.StdEncoding.DecodeString(secret.SecretValueIV) + if err != nil { + return nil, fmt.Errorf("unable to decode secret IV for secret value") + } + + value_tag, err := base64.StdEncoding.DecodeString(secret.SecretValueTag) + if err != nil { + return nil, fmt.Errorf("unable to decode secret authentication tag for secret value") + } + + value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValueCiphertext) + if err != nil { + return nil, fmt.Errorf("unable to decode secret cipher text for secret key") + } + + plainTextValue, err := crypto.DecryptSymmetric(key, value_ciphertext, value_tag, value_iv) + if err != nil { + return nil, fmt.Errorf("unable to symmetrically decrypt secret value") + } + + plainTextSecret := models.SingleEnvironmentVariable{ + Key: string(plainTextKey), + Value: string(plainTextValue), + Type: string(secret.Type), + ID: secret.ID, + } + + plainTextSecrets = append(plainTextSecrets, plainTextSecret) } - return false + + return plainTextSecrets, nil } diff --git a/cli/packages/util/vault.go b/cli/packages/util/vault.go index 03d03b360..63a014af1 100644 --- a/cli/packages/util/vault.go +++ b/cli/packages/util/vault.go @@ -29,13 +29,13 @@ func GetKeyRing() (keyring.Keyring, error) { keyringInstanceConfig := keyring.Config{ FilePasswordFunc: fileKeyringPassphrasePrompt, - ServiceName: SERVICE_NAME, - LibSecretCollectionName: SERVICE_NAME, - KWalletAppID: SERVICE_NAME, - KWalletFolder: SERVICE_NAME, + ServiceName: KEYRING_SERVICE_NAME, + LibSecretCollectionName: KEYRING_SERVICE_NAME, + KWalletAppID: KEYRING_SERVICE_NAME, + KWalletFolder: KEYRING_SERVICE_NAME, KeychainTrustApplication: true, - WinCredPrefix: SERVICE_NAME, - FileDir: fmt.Sprintf("~/%s-file-vault", SERVICE_NAME), + WinCredPrefix: KEYRING_SERVICE_NAME, + FileDir: fmt.Sprintf("~/%s-file-vault", KEYRING_SERVICE_NAME), KeychainAccessibleWhenUnlocked: true, }