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 }