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  }