diff --git a/k8-operator/go.mod b/k8-operator/go.mod index 3e838b64dc..44847bf725 100644 --- a/k8-operator/go.mod +++ b/k8-operator/go.mod @@ -31,6 +31,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-resty/resty/v2 v2.7.0 github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -56,7 +57,7 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.21.0 // indirect - golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect + golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect diff --git a/k8-operator/go.sum b/k8-operator/go.sum index 3a4aa4557d..a0d400d6d2 100644 --- a/k8-operator/go.sum +++ b/k8-operator/go.sum @@ -138,6 +138,8 @@ github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -435,6 +437,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= diff --git a/k8-operator/packages/auth/api.go b/k8-operator/packages/auth/api.go new file mode 100644 index 0000000000..28939147ff --- /dev/null +++ b/k8-operator/packages/auth/api.go @@ -0,0 +1,108 @@ +package api + +import ( + "encoding/base64" + "errors" + "fmt" + "strings" + + "github.com/Infisical/infisical/k8-operator/packages/crypto" + "github.com/Infisical/infisical/k8-operator/packages/models" + "github.com/go-resty/resty/v2" + "golang.org/x/crypto/nacl/box" +) + +const INFISICAL_URL = "https://app.infisical.com/api" + +func GetAllEnvironmentVariables(projectId string, envName string, infisicalToken string) ([]models.SingleEnvironmentVariable, error) { + envsFromApi, err := GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, projectId) + if err != nil { + return nil, err + } + + return envsFromApi, 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). + 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)) + + 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) + 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 := crypto.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 := crypto.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 +} diff --git a/k8-operator/packages/crypto/crypto.go b/k8-operator/packages/crypto/crypto.go new file mode 100644 index 0000000000..d5b0a99556 --- /dev/null +++ b/k8-operator/packages/crypto/crypto.go @@ -0,0 +1,28 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" +) + +func DecryptSymmetric(key []byte, encryptedPrivateKey []byte, tag []byte, IV []byte) ([]byte, error) { + 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 +} diff --git a/k8-operator/packages/models/api.go b/k8-operator/packages/models/api.go new file mode 100644 index 0000000000..57420dac57 --- /dev/null +++ b/k8-operator/packages/models/api.go @@ -0,0 +1,51 @@ +package models + +import "time" + +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 SingleEnvironmentVariable struct { + Key string `json:"key"` + Value string `json:"value"` +}