update k8 operator to use service account

This commit is contained in:
Maidul Islam
2023-04-16 16:51:36 -07:00
parent 56c35293eb
commit 619fe553ef
13 changed files with 388 additions and 30 deletions
+57
View File
@@ -78,3 +78,60 @@ func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Req
return encryptedSecretsResponse, nil
}
func CallGetServiceTokenAccountDetailsV2(httpClient *resty.Client) (ServiceAccountDetailsResponse, error) {
var serviceAccountDetailsResponse ServiceAccountDetailsResponse
response, err := httpClient.
R().
SetResult(&serviceAccountDetailsResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
Get(fmt.Sprintf("%v/v2/service-accounts/me", API_HOST_URL))
if err != nil {
return ServiceAccountDetailsResponse{}, fmt.Errorf("CallGetServiceTokenAccountDetailsV2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return ServiceAccountDetailsResponse{}, fmt.Errorf("CallGetServiceTokenAccountDetailsV2: Unsuccessful response: [response=%s]", response)
}
return serviceAccountDetailsResponse, nil
}
func CallGetServiceAccountWorkspacePermissionsV2(httpClient *resty.Client) (ServiceAccountWorkspacePermissions, error) {
var serviceAccountWorkspacePermissionsResponse ServiceAccountWorkspacePermissions
response, err := httpClient.
R().
SetResult(&serviceAccountWorkspacePermissionsResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
Get(fmt.Sprintf("%v/v2/service-accounts/<service-account-id>/permissions/workspace", API_HOST_URL))
if err != nil {
return ServiceAccountWorkspacePermissions{}, fmt.Errorf("CallGetServiceAccountWorkspacePermissionsV2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return ServiceAccountWorkspacePermissions{}, fmt.Errorf("CallGetServiceAccountWorkspacePermissionsV2: Unsuccessful response: [response=%s]", response)
}
return serviceAccountWorkspacePermissionsResponse, nil
}
func CallGetServiceAccountKeysV2(httpClient *resty.Client, request GetServiceAccountKeysRequest) (GetServiceAccountKeysResponse, error) {
var serviceAccountKeysResponse GetServiceAccountKeysResponse
response, err := httpClient.
R().
SetResult(&serviceAccountKeysResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
Get(fmt.Sprintf("%v/v2/service-accounts/%v/keys", API_HOST_URL, request.ServiceAccountId))
if err != nil {
return GetServiceAccountKeysResponse{}, fmt.Errorf("CallGetServiceAccountKeysV2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetServiceAccountKeysResponse{}, fmt.Errorf("CallGetServiceAccountKeysV2: Unsuccessful response: [response=%s]", response)
}
return serviceAccountKeysResponse, nil
}
+53
View File
@@ -65,3 +65,56 @@ type GetServiceTokenDetailsResponse struct {
Iv string `json:"iv"`
Tag string `json:"tag"`
}
type ServiceAccountDetailsResponse struct {
ServiceAccount struct {
ID string `json:"_id"`
Name string `json:"name"`
Organization string `json:"organization"`
PublicKey string `json:"publicKey"`
LastUsed time.Time `json:"lastUsed"`
ExpiresAt time.Time `json:"expiresAt"`
} `json:"serviceAccount"`
}
type ServiceAccountWorkspacePermission struct {
ID string `json:"_id"`
ServiceAccount string `json:"serviceAccount"`
Workspace struct {
ID string `json:"_id"`
Name string `json:"name"`
AutoCapitalization bool `json:"autoCapitalization"`
Organization string `json:"organization"`
Environments []struct {
Name string `json:"name"`
Slug string `json:"slug"`
ID string `json:"_id"`
} `json:"environments"`
} `json:"workspace"`
Environment string `json:"environment"`
Read bool `json:"read"`
Write bool `json:"write"`
}
type ServiceAccountWorkspacePermissions struct {
ServiceAccountWorkspacePermission []ServiceAccountWorkspacePermissions `json:"serviceAccountWorkspacePermissions"`
}
type GetServiceAccountKeysRequest struct {
ServiceAccountId string `json:"id"`
}
type ServiceAccountKey struct {
ID string `json:"_id"`
EncryptedKey string `json:"encryptedKey"`
Nonce string `json:"nonce"`
Sender string `json:"sender"`
ServiceAccount string `json:"serviceAccount"`
Workspace string `json:"workspace"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type GetServiceAccountKeysResponse struct {
ServiceAccountKeys []ServiceAccountKey `json:"serviceAccountKeys"`
}
+7
View File
@@ -3,6 +3,8 @@ package crypto
import (
"crypto/aes"
"crypto/cipher"
"golang.org/x/crypto/nacl/box"
)
func DecryptSymmetric(key []byte, encryptedPrivateKey []byte, tag []byte, IV []byte) ([]byte, error) {
@@ -26,3 +28,8 @@ func DecryptSymmetric(key []byte, encryptedPrivateKey []byte, tag []byte, IV []b
return plaintext, nil
}
func DecryptAsymmetric(ciphertext []byte, nonce []byte, publicKey []byte, privateKey []byte) (plainText []byte) {
plainTextToReturn, _ := box.Open(nil, ciphertext, (*[24]byte)(nonce), (*[32]byte)(publicKey), (*[32]byte)(privateKey))
return plainTextToReturn
}
+14
View File
@@ -0,0 +1,14 @@
package model
type ServiceAccountDetails struct {
AccessKey string
PublicKey string
PrivateKey string
}
type SingleEnvironmentVariable struct {
Key string `json:"key"`
Value string `json:"value"`
Type string `json:"type"`
ID string `json:"_id"`
}
+76 -11
View File
@@ -7,16 +7,10 @@ import (
"github.com/Infisical/infisical/k8-operator/packages/api"
"github.com/Infisical/infisical/k8-operator/packages/crypto"
"github.com/Infisical/infisical/k8-operator/packages/model"
"github.com/go-resty/resty/v2"
)
type SingleEnvironmentVariable struct {
Key string `json:"key"`
Value string `json:"value"`
Type string `json:"type"`
ID string `json:"_id"`
}
type DecodedSymmetricEncryptionDetails = struct {
Cipher []byte
IV []byte
@@ -54,7 +48,7 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe
return serviceTokenDetails, nil
}
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([]SingleEnvironmentVariable, api.GetEncryptedSecretsV2Response, error) {
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([]model.SingleEnvironmentVariable, api.GetEncryptedSecretsV2Response, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
@@ -100,6 +94,77 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([
return plainTextSecrets, encryptedSecretsResponse, nil
}
// Fetches plaintext secrets from an API endpoint using a service account.
// The function fetches the service account details and keys, decrypts the workspace key, fetches the encrypted secrets for the specified project and environment, and decrypts the secrets using the decrypted workspace key.
// Returns the plaintext secrets, encrypted secrets response, and any errors that occurred during the process.
func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, api.GetEncryptedSecretsV2Response, error) {
httpClient := resty.New()
httpClient.SetAuthToken(serviceAccountCreds.AccessKey).
SetHeader("Accept", "application/json")
serviceAccountDetails, err := api.CallGetServiceTokenAccountDetailsV2(httpClient)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err)
}
serviceAccountKeys, err := api.CallGetServiceAccountKeysV2(httpClient, api.GetServiceAccountKeysRequest{ServiceAccountId: serviceAccountDetails.ServiceAccount.ID})
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err)
}
// find key for requested project
var workspaceServiceAccountKey api.ServiceAccountKey
for _, serviceAccountKey := range serviceAccountKeys.ServiceAccountKeys {
if serviceAccountKey.Workspace == projectId {
workspaceServiceAccountKey = serviceAccountKey
}
}
if workspaceServiceAccountKey.ID == "" || workspaceServiceAccountKey.EncryptedKey == "" || workspaceServiceAccountKey.Nonce == "" || serviceAccountCreds.PublicKey == "" || serviceAccountCreds.PrivateKey == "" {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err)
}
cipherText, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.EncryptedKey)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode EncryptedKey secrets because [err=%v]", err)
}
nonce, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.Nonce)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode nonce secrets because [err=%v]", err)
}
publickey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PublicKey)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PublicKey secrets because [err=%v]", err)
}
privateKey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PrivateKey)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PrivateKey secrets because [err=%v]", err)
}
plainTextWorkspaceKey := crypto.DecryptAsymmetric(cipherText, nonce, publickey, privateKey)
encryptedSecretsResponse, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
WorkspaceId: projectId,
Environment: environmentName,
ETag: etag,
})
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to fetch secrets because [err=%v]", err)
}
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get plain text secrets because [err=%v]", err)
}
return plainTextSecrets, encryptedSecretsResponse, nil
}
func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) {
cipherx, err := base64.StdEncoding.DecodeString(cipher)
if err != nil {
@@ -129,8 +194,8 @@ func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV st
}, nil
}
func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) ([]SingleEnvironmentVariable, error) {
plainTextSecrets := []SingleEnvironmentVariable{}
func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) ([]model.SingleEnvironmentVariable, error) {
plainTextSecrets := []model.SingleEnvironmentVariable{}
for _, secret := range encryptedSecretsResponse.Secrets {
// Decrypt key
key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV)
@@ -174,7 +239,7 @@ func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSe
return nil, fmt.Errorf("unable to symmetrically decrypt secret value")
}
plainTextSecret := SingleEnvironmentVariable{
plainTextSecret := model.SingleEnvironmentVariable{
Key: string(plainTextKey),
Value: string(plainTextValue),
Type: string(secret.Type),