github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/rpc_backup_handlers.go (about)

     1  package gateway
     2  
     3  import (
     4  	"crypto/aes"
     5  	"crypto/cipher"
     6  	"crypto/rand"
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"errors"
    10  	"io"
    11  	"strings"
    12  
    13  	"github.com/sirupsen/logrus"
    14  
    15  	"github.com/TykTechnologies/tyk/config"
    16  	"github.com/TykTechnologies/tyk/storage"
    17  	"github.com/TykTechnologies/tyk/user"
    18  )
    19  
    20  const RPCKeyPrefix = "rpc:"
    21  const BackupApiKeyBase = "node-definition-backup:"
    22  const BackupPolicyKeyBase = "node-policy-backup:"
    23  
    24  func getTagListAsString() string {
    25  	tagList := ""
    26  	if tags := config.Global().DBAppConfOptions.Tags; len(tags) > 0 {
    27  		tagList = strings.Join(tags, "-")
    28  	}
    29  
    30  	return tagList
    31  }
    32  
    33  func LoadDefinitionsFromRPCBackup() ([]*APISpec, error) {
    34  	tagList := getTagListAsString()
    35  	checkKey := BackupApiKeyBase + tagList
    36  
    37  	store := storage.RedisCluster{KeyPrefix: RPCKeyPrefix}
    38  	connected := store.Connect()
    39  	log.Info("[RPC] --> Loading API definitions from backup")
    40  
    41  	if !connected {
    42  		return nil, errors.New("[RPC] --> RPC Backup recovery failed: redis connection failed")
    43  	}
    44  
    45  	secret := rightPad2Len(config.Global().Secret, "=", 32)
    46  	cryptoText, err := store.GetKey(checkKey)
    47  	apiListAsString := decrypt([]byte(secret), cryptoText)
    48  
    49  	if err != nil {
    50  		return nil, errors.New("[RPC] --> Failed to get node backup (" + checkKey + "): " + err.Error())
    51  	}
    52  
    53  	a := APIDefinitionLoader{}
    54  	return a.processRPCDefinitions(apiListAsString)
    55  }
    56  
    57  func saveRPCDefinitionsBackup(list string) error {
    58  	if !json.Valid([]byte(list)) {
    59  		return errors.New("--> RPC Backup save failure: wrong format, skipping.")
    60  	}
    61  
    62  	log.Info("Storing RPC Definitions backup")
    63  	tagList := getTagListAsString()
    64  
    65  	log.Info("--> Connecting to DB")
    66  
    67  	store := storage.RedisCluster{KeyPrefix: RPCKeyPrefix}
    68  	connected := store.Connect()
    69  
    70  	log.Info("--> Connected to DB")
    71  
    72  	if !connected {
    73  		return errors.New("--> RPC Backup save failed: redis connection failed")
    74  	}
    75  
    76  	secret := rightPad2Len(config.Global().Secret, "=", 32)
    77  	cryptoText := encrypt([]byte(secret), list)
    78  	err := store.SetKey(BackupApiKeyBase+tagList, cryptoText, -1)
    79  	if err != nil {
    80  		return errors.New("Failed to store node backup: " + err.Error())
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  func LoadPoliciesFromRPCBackup() (map[string]user.Policy, error) {
    87  	tagList := getTagListAsString()
    88  	checkKey := BackupPolicyKeyBase + tagList
    89  
    90  	store := storage.RedisCluster{KeyPrefix: RPCKeyPrefix}
    91  
    92  	connected := store.Connect()
    93  	log.Info("[RPC] Loading Policies from backup")
    94  
    95  	if !connected {
    96  		return nil, errors.New("[RPC] --> RPC Policy Backup recovery failed: redis connection failed")
    97  	}
    98  
    99  	secret := rightPad2Len(config.Global().Secret, "=", 32)
   100  	cryptoText, err := store.GetKey(checkKey)
   101  	listAsString := decrypt([]byte(secret), cryptoText)
   102  
   103  	if err != nil {
   104  		return nil, errors.New("[RPC] --> Failed to get node policy backup (" + checkKey + "): " + err.Error())
   105  	}
   106  
   107  	if policies, err := parsePoliciesFromRPC(listAsString); err != nil {
   108  		log.WithFields(logrus.Fields{
   109  			"prefix": "policy",
   110  		}).Error("Failed decode: ", err)
   111  		return nil, err
   112  	} else {
   113  		return policies, nil
   114  	}
   115  }
   116  
   117  func saveRPCPoliciesBackup(list string) error {
   118  	if !json.Valid([]byte(list)) {
   119  		return errors.New("--> RPC Backup save failure: wrong format, skipping.")
   120  	}
   121  
   122  	log.Info("Storing RPC policies backup")
   123  	tagList := getTagListAsString()
   124  
   125  	log.Info("--> Connecting to DB")
   126  
   127  	store := storage.RedisCluster{KeyPrefix: RPCKeyPrefix}
   128  	connected := store.Connect()
   129  
   130  	log.Info("--> Connected to DB")
   131  
   132  	if !connected {
   133  		return errors.New("--> RPC Backup save failed: redis connection failed")
   134  	}
   135  
   136  	secret := rightPad2Len(config.Global().Secret, "=", 32)
   137  	cryptoText := encrypt([]byte(secret), list)
   138  	err := store.SetKey(BackupPolicyKeyBase+tagList, cryptoText, -1)
   139  	if err != nil {
   140  		return errors.New("Failed to store node backup: " + err.Error())
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  // encrypt string to base64 crypto using AES
   147  func encrypt(key []byte, text string) string {
   148  	plaintext := []byte(text)
   149  
   150  	block, err := aes.NewCipher(key)
   151  	if err != nil {
   152  		log.Error(err)
   153  		return ""
   154  	}
   155  
   156  	// The IV needs to be unique, but not secure. Therefore it's common to
   157  	// include it at the beginning of the ciphertext.
   158  	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
   159  	iv := ciphertext[:aes.BlockSize]
   160  	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
   161  		log.Error(err)
   162  		return ""
   163  	}
   164  
   165  	stream := cipher.NewCFBEncrypter(block, iv)
   166  	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
   167  
   168  	// convert to base64
   169  	return base64.URLEncoding.EncodeToString(ciphertext)
   170  }
   171  
   172  // decrypt from base64 to decrypted string
   173  func decrypt(key []byte, cryptoText string) string {
   174  	ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText)
   175  
   176  	block, err := aes.NewCipher(key)
   177  	if err != nil {
   178  		log.Error(err)
   179  		return ""
   180  	}
   181  
   182  	// The IV needs to be unique, but not secure. Therefore it's common to
   183  	// include it at the beginning of the ciphertext.
   184  	if len(ciphertext) < aes.BlockSize {
   185  		log.Error("ciphertext too short")
   186  		return ""
   187  	}
   188  	iv := ciphertext[:aes.BlockSize]
   189  	ciphertext = ciphertext[aes.BlockSize:]
   190  
   191  	stream := cipher.NewCFBDecrypter(block, iv)
   192  
   193  	// XORKeyStream can work in-place if the two arguments are the same.
   194  	stream.XORKeyStream(ciphertext, ciphertext)
   195  
   196  	return string(ciphertext)
   197  }
   198  
   199  func rightPad2Len(s, padStr string, overallLen int) string {
   200  	padCountInt := 1 + (overallLen-len(padStr))/len(padStr)
   201  	retStr := s + strings.Repeat(padStr, padCountInt)
   202  	return retStr[:overallLen]
   203  }