github.com/kotalco/kotal@v0.3.0/clients/bitcoin/bitcoin_core_client.go (about) 1 package bitcoin 2 3 import ( 4 "context" 5 "crypto/hmac" 6 "crypto/rand" 7 "crypto/sha256" 8 "encoding/hex" 9 "fmt" 10 "strings" 11 12 bitcoinv1alpha1 "github.com/kotalco/kotal/apis/bitcoin/v1alpha1" 13 "github.com/kotalco/kotal/controllers/shared" 14 corev1 "k8s.io/api/core/v1" 15 "k8s.io/apimachinery/pkg/types" 16 "sigs.k8s.io/controller-runtime/pkg/client" 17 ) 18 19 // BitcoinCoreClient is Bitcoin core client 20 // https://github.com/bitcoin/bitcoin 21 type BitcoinCoreClient struct { 22 node *bitcoinv1alpha1.Node 23 client client.Client 24 } 25 26 var hashCash map[string]string = map[string]string{} 27 28 // Images 29 const ( 30 // BitcoinCoreHomeDir is Bitcoin core image home dir 31 BitcoinCoreHomeDir = "/data" 32 ) 33 34 // Command returns environment variables for the client 35 func (c *BitcoinCoreClient) Env() (env []corev1.EnvVar) { 36 env = append(env, corev1.EnvVar{ 37 Name: EnvBitcoinData, 38 Value: shared.PathData(c.HomeDir()), 39 }) 40 41 return 42 } 43 44 // Command is Bitcoin core client entrypoint 45 func (c *BitcoinCoreClient) Command() (command []string) { 46 command = append(command, "bitcoind") 47 return 48 } 49 50 // Args returns Bitcoin core client args 51 func (c *BitcoinCoreClient) Args() (args []string) { 52 node := c.node 53 54 networks := map[string]string{ 55 "mainnet": "main", 56 "testnet": "test", 57 } 58 59 // convert bool to 0 or 1 60 var Btoi = func(b bool) uint { 61 if b { 62 return 1 63 } 64 return 0 65 } 66 67 args = append(args, fmt.Sprintf("%s=%s", BitcoinArgDataDir, shared.PathData(c.HomeDir()))) 68 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgListen, Btoi(*node.Spec.Listen))) 69 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgMaxConnections, *node.Spec.MaxConnections)) 70 args = append(args, fmt.Sprintf("%s=%s", BitcoinArgChain, networks[string(node.Spec.Network)])) 71 args = append(args, fmt.Sprintf("%s=%s:%d", BitcoinArgBind, shared.Host(true), node.Spec.P2PPort)) 72 73 if c.node.Spec.RPC { 74 args = append(args, fmt.Sprintf("%s=1", BitcoinArgServer)) 75 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgRPCPort, node.Spec.RPCPort)) 76 args = append(args, fmt.Sprintf("%s=%s", BitcoinArgRPCBind, shared.Host(node.Spec.RPC))) 77 args = append(args, fmt.Sprintf("%s=%s/0", BitcoinArgRPCAllowIp, shared.Host(node.Spec.RPC))) 78 79 // TODO: mock k8s secret getter to test rpc users and whitelist 80 for _, rpcUser := range node.Spec.RPCUsers { 81 name := types.NamespacedName{Name: rpcUser.PasswordSecretName, Namespace: node.Namespace} 82 password, _ := shared.GetSecret(context.TODO(), c.client, name, "password") 83 saltedHash, found := hashCash[password] 84 if !found { 85 salt, hash := HmacSha256(password) 86 saltedHash = fmt.Sprintf("%s$%s", salt, hash) 87 hashCash[password] = saltedHash 88 } 89 args = append(args, fmt.Sprintf("%s=%s:%s", BitcoinArgRPCAuth, rpcUser.Username, saltedHash)) 90 91 if len(node.Spec.RPCWhitelist) != 0 { 92 list := strings.Join(node.Spec.RPCWhitelist, ",") 93 args = append(args, fmt.Sprintf("%s=%s:%s", BitcoinArgRpcWhitelist, rpcUser.Username, list)) 94 } 95 } 96 97 } else { 98 args = append(args, fmt.Sprintf("%s=0", BitcoinArgServer)) 99 } 100 101 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgReIndex, Btoi(node.Spec.ReIndex))) 102 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgTransactionIndex, Btoi(node.Spec.TransactionIndex))) 103 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgBlocksOnly, Btoi(node.Spec.BlocksOnly))) 104 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgCoinStatsIndex, Btoi(node.Spec.CoinStatsIndex))) 105 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgPrune, Btoi(node.Spec.Pruning))) 106 107 args = append(args, fmt.Sprintf("%s=%d", BitcoinArgDBCacheSize, node.Spec.DBCacheSize)) 108 109 if !node.Spec.Wallet { 110 args = append(args, BitcoinArgDisableWallet) 111 } 112 113 return 114 } 115 116 // HomeDir is the home directory of Bitcoin core client image 117 func (c *BitcoinCoreClient) HomeDir() string { 118 return BitcoinCoreHomeDir 119 } 120 121 // HmacSha256 creates new hmac sha256 hash 122 // reference implementation: 123 // https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py 124 func HmacSha256(password string) (salt, hash string) { 125 random := make([]byte, 16) 126 rand.Read(random) 127 salt = hex.EncodeToString(random) 128 129 h := hmac.New(sha256.New, []byte(salt)) 130 h.Write([]byte(password)) 131 132 hash = fmt.Sprintf("%x", h.Sum(nil)) 133 134 return 135 }