Cluster secret: Docs, error handling, internal key mgmt.
This commit is contained in:
parent
98335901fc
commit
90d1e97a8e
|
@ -91,6 +91,7 @@ This will install `ipfs-cluster-service` and `ipfs-cluster-ctl` in your `$GOPATH
|
|||
**`ipfs-cluster-service`** runs an ipfs-cluster peer:
|
||||
|
||||
* Initialize with `ipfs-cluster-service init`
|
||||
* You will be asked to enter a cluster secret. For more on this, see [the Cluster secret section of the `ipfs-cluster-service` * README](ipfs-cluster-service/dist/README.md#cluster-secret).
|
||||
* Run with `ipfs-cluster-service`. Check `--help` for options
|
||||
|
||||
For more information see the [`ipfs-cluster-service` README](ipfs-cluster-service/dist/README.md). Also, read [A guide to running IPFS Cluster](docs/ipfs-cluster-guide.md) for full a full overview of how cluster works.
|
||||
|
|
47
config.go
47
config.go
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -37,8 +38,8 @@ type Config struct {
|
|||
PrivateKey crypto.PrivKey
|
||||
|
||||
// Cluster secret for private network. Peers will be in the same cluster if and
|
||||
// only if they have the same ClusterSecret. If this value is empty, then the
|
||||
// cluster will be on a unprotected public network (accessible by anyone).
|
||||
// only if they have the same ClusterSecret. The cluster secret must be exactly
|
||||
// 64 characters and contain only hexadecimal characters (`[0-9a-f]`).
|
||||
ClusterSecret []byte
|
||||
|
||||
// ClusterPeers is the list of peers in the Cluster. They are used
|
||||
|
@ -111,8 +112,8 @@ type JSONConfig struct {
|
|||
PrivateKey string `json:"private_key"`
|
||||
|
||||
// Cluster secret for private network. Peers will be in the same cluster if and
|
||||
// only if they have the same ClusterSecret. If this value is empty, then the
|
||||
// cluster will be on a unprotected public network (accessible by anyone).
|
||||
// only if they have the same ClusterSecret. The cluster secret must be exactly
|
||||
// 64 characters and contain only hexadecimal characters (`[0-9a-f]`).
|
||||
ClusterSecret string `json:"cluster_secret"`
|
||||
|
||||
// ClusterPeers is the list of peers' multiaddresses in the Cluster.
|
||||
|
@ -206,7 +207,7 @@ func (cfg *Config) ToJSONConfig() (j *JSONConfig, err error) {
|
|||
j = &JSONConfig{
|
||||
ID: cfg.ID.Pretty(),
|
||||
PrivateKey: pKey,
|
||||
ClusterSecret: string(cfg.ClusterSecret),
|
||||
ClusterSecret: EncodeClusterSecret(cfg.ClusterSecret),
|
||||
ClusterPeers: clusterPeers,
|
||||
Bootstrap: bootstrap,
|
||||
LeaveOnShutdown: cfg.LeaveOnShutdown,
|
||||
|
@ -243,6 +244,11 @@ func (jcfg *JSONConfig) ToConfig() (c *Config, err error) {
|
|||
err = fmt.Errorf("error parsing private_key ID: %s", err)
|
||||
return
|
||||
}
|
||||
clusterSecret, err := DecodeClusterSecret(jcfg.ClusterSecret)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error loading cluster secret from config: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clusterPeers := make([]ma.Multiaddr, len(jcfg.ClusterPeers))
|
||||
for i := 0; i < len(jcfg.ClusterPeers); i++ {
|
||||
|
@ -312,7 +318,7 @@ func (jcfg *JSONConfig) ToConfig() (c *Config, err error) {
|
|||
c = &Config{
|
||||
ID: id,
|
||||
PrivateKey: pKey,
|
||||
ClusterSecret: []byte(jcfg.ClusterSecret),
|
||||
ClusterSecret: clusterSecret,
|
||||
ClusterPeers: clusterPeers,
|
||||
Bootstrap: bootstrap,
|
||||
LeaveOnShutdown: jcfg.LeaveOnShutdown,
|
||||
|
@ -410,26 +416,39 @@ func (cfg *Config) unshadow() {
|
|||
}
|
||||
|
||||
func generateClusterSecret() ([]byte, error) {
|
||||
encodedSecretLength := 64
|
||||
secret := make([]byte, base64.StdEncoding.DecodedLen(encodedSecretLength))
|
||||
_, err := crand.Read(secret)
|
||||
secretBytes := make([]byte, 32)
|
||||
_, err := crand.Read(secretBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading from rand: %v", err)
|
||||
}
|
||||
encodedSecret := make([]byte, encodedSecretLength)
|
||||
base64.StdEncoding.Encode(encodedSecret, secret)
|
||||
return encodedSecret, nil
|
||||
return secretBytes, nil
|
||||
}
|
||||
|
||||
func clusterSecretToKey(secret []byte) (string, error) {
|
||||
var key bytes.Buffer
|
||||
key.WriteString("/key/swarm/psk/1.0.0/\n")
|
||||
key.WriteString("/base64/\n")
|
||||
key.Write(secret)
|
||||
key.WriteString("/base16/\n")
|
||||
key.WriteString(EncodeClusterSecret(secret))
|
||||
|
||||
return key.String(), nil
|
||||
}
|
||||
|
||||
func DecodeClusterSecret(hexSecret string) ([]byte, error) {
|
||||
secret, err := hex.DecodeString(hexSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretLen := len(secret)
|
||||
if secretLen != 32 {
|
||||
return nil, fmt.Errorf("Input secret is %d bytes, cluster secret should be 32.", secretLen)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func EncodeClusterSecret(secretBytes []byte) string {
|
||||
return hex.EncodeToString(secretBytes)
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration object with a randomly
|
||||
// generated ID and private key.
|
||||
func NewDefaultConfig() (*Config, error) {
|
||||
|
|
|
@ -2,13 +2,13 @@ package ipfscluster
|
|||
|
||||
import "testing"
|
||||
|
||||
var testingClusterSecret = []byte("2951539a3737c06a5aee55834c27145ca1783bdc7daeaa92f9712b3ff6e9fa25")
|
||||
var testingClusterSecret, _ = DecodeClusterSecret("2588b80d5cb05374fa142aed6cbb047d1f4ef8ef15e37eba68c65b9d30df67ed")
|
||||
|
||||
func testingConfig() *Config {
|
||||
jcfg := &JSONConfig{
|
||||
ID: "QmUfSFm12eYCaRdypg48m8RqkXfLW7A2ZeGZb2skeHHDGA",
|
||||
PrivateKey: "CAASqAkwggSkAgEAAoIBAQDpT16IRF6bb9tHsCbQ7M+nb2aI8sz8xyt8PoAWM42ki+SNoESIxKb4UhFxixKvtEdGxNE6aUUVc8kFk6wTStJ/X3IGiMetwkXiFiUxabUF/8A6SyvnSVDm+wFuavugpVrZikjLcfrf2xOVgnG3deQQvd/qbAv14jTwMFl+T+8d/cXBo8Mn/leLZCQun/EJEnkXP5MjgNI8XcWUE4NnH3E0ESSm6Pkm8MhMDZ2fmzNgqEyJ0GVinNgSml3Pyha3PBSj5LRczLip/ie4QkKx5OHvX2L3sNv/JIUHse5HSbjZ1c/4oGCYMVTYCykWiczrxBUOlcr8RwnZLOm4n2bCt5ZhAgMBAAECggEAVkePwfzmr7zR7tTpxeGNeXHtDUAdJm3RWwUSASPXgb5qKyXVsm5nAPX4lXDE3E1i/nzSkzNS5PgIoxNVU10cMxZs6JW0okFx7oYaAwgAddN6lxQtjD7EuGaixN6zZ1k/G6vT98iS6i3uNCAlRZ9HVBmjsOF8GtYolZqLvfZ5izEVFlLVq/BCs7Y5OrDrbGmn3XupfitVWYExV0BrHpobDjsx2fYdTZkmPpSSvXNcm4Iq2AXVQzoqAfGo7+qsuLCZtVlyTfVKQjMvE2ffzN1dQunxixOvev/fz4WSjGnRpC6QLn6Oqps9+VxQKqKuXXqUJC+U45DuvA94Of9MvZfAAQKBgQD7xmXueXRBMr2+0WftybAV024ap0cXFrCAu+KWC1SUddCfkiV7e5w+kRJx6RH1cg4cyyCL8yhHZ99Z5V0Mxa/b/usuHMadXPyX5szVI7dOGgIC9q8IijN7B7GMFAXc8+qC7kivehJzjQghpRRAqvRzjDls4gmbNPhbH1jUiU124QKBgQDtOaW5/fOEtOq0yWbDLkLdjImct6oKMLhENL6yeIKjMYgifzHb2adk7rWG3qcMrdgaFtDVfqv8UmMEkzk7bSkovMVj3SkLzMz84ii1SkSfyaCXgt/UOzDkqAUYB0cXMppYA7jxHa2OY8oEHdBgmyJXdLdzJxCp851AoTlRUSePgQKBgQCQgKgUHOUaXnMEx88sbOuBO14gMg3dNIqM+Ejt8QbURmI8k3arzqA4UK8Tbb9+7b0nzXWanS5q/TT1tWyYXgW28DIuvxlHTA01aaP6WItmagrphIelERzG6f1+9ib/T4czKmvROvDIHROjq8lZ7ERs5Pg4g+sbh2VbdzxWj49EQQKBgFEna36ZVfmMOs7mJ3WWGeHY9ira2hzqVd9fe+1qNKbHhx7mDJR9fTqWPxuIh/Vac5dZPtAKqaOEO8OQ6f9edLou+ggT3LrgsS/B3tNGOPvA6mNqrk/Yf/15TWTO+I8DDLIXc+lokbsogC+wU1z5NWJd13RZZOX/JUi63vTmonYBAoGBAIpglLCH2sPXfmguO6p8QcQcv4RjAU1c0GP4P5PNN3Wzo0ItydVd2LHJb6MdmL6ypeiwNklzPFwTeRlKTPmVxJ+QPg1ct/3tAURN/D40GYw9ojDhqmdSl4HW4d6gHS2lYzSFeU5jkG49y5nirOOoEgHy95wghkh6BfpwHujYJGw4",
|
||||
ClusterSecret: string(testingClusterSecret),
|
||||
ClusterSecret: EncodeClusterSecret(testingClusterSecret),
|
||||
|
||||
ClusterListenMultiaddress: "/ip4/127.0.0.1/tcp/10000",
|
||||
APIListenMultiaddress: "/ip4/127.0.0.1/tcp/10002",
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
This step creates a single-node IPFS Cluster.
|
||||
|
||||
First initialize the configuration:
|
||||
First initialize the configuration (see [the Cluster secret section of the `ipfs-cluster-service` README](ipfs-cluster-service/dist/README.md#cluster-secret) for more info on entering a cluster secret):
|
||||
|
||||
```
|
||||
node0 $ ipfs-cluster-service init
|
||||
Enter cluster secret (to automatically generate, rerun with --gen-secret): <enter-your-secret>
|
||||
ipfs-cluster-service configuration written to /home/user/.ipfs-cluster/service.json
|
||||
```
|
||||
|
||||
|
|
21
ipfs-cluster-service/dist/README.md
vendored
21
ipfs-cluster-service/dist/README.md
vendored
|
@ -22,6 +22,26 @@ Before running `ipfs-cluster-service` for the first time, initialize a configura
|
|||
$ ipfs-cluster-service init
|
||||
```
|
||||
|
||||
### Cluster secret
|
||||
|
||||
As long as the `CLUSTER_SECRET` environment variable is not set, you will be
|
||||
prompted to enter a `cluster_secret` value for your configuration. Two peers
|
||||
will be in the same cluster if and only if they share the same `cluster_secret`
|
||||
value. When initializing a cluster service, the following conditions are checked
|
||||
(in the order shown) and determine how `cluster_secret` is set:
|
||||
|
||||
1. If the `--gen-secret` flag is passed to `ipfs-cluster-service init`, then a
|
||||
`cluster_secret` value will be automatically generated.
|
||||
2. If the `CLUSTER_SECRET` environment variable is set in your current shell
|
||||
instance, its value will be read and used as the `cluster_service` value.
|
||||
3. If neither of the above conditions were ture, then you will be prompted to
|
||||
enter a `cluster_secret` during the initialization.
|
||||
|
||||
The `cluster_secret` must be a 64-character string with only hexadecimal
|
||||
characters (`[0-9a-f]`).
|
||||
|
||||
TODO: Explain adding peers/sharing cluster secret once that functionality is
|
||||
implemented.
|
||||
|
||||
### Configuration
|
||||
|
||||
|
@ -33,6 +53,7 @@ You can add the multiaddresses for the other cluster peers the `bootstrap` varia
|
|||
{
|
||||
"id": "QmXMhZ53zAoes8TYbKGn3rnm5nfWs5Wdu41Fhhfw9XmM5A",
|
||||
"private_key": "<redacted>",
|
||||
"cluster_secret": "<redacted>",
|
||||
"cluster_peers": [],
|
||||
"bootstrap": [],
|
||||
"leave_on_shutdown": false,
|
||||
|
|
|
@ -204,7 +204,7 @@ func main() {
|
|||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
initConfig(c.GlobalBool("force"), c.Bool("gen-secret"),
|
||||
[]byte(c.GlobalString("env-cluster-secret")))
|
||||
c.GlobalString("env-cluster-secret"))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
@ -238,7 +238,7 @@ func main() {
|
|||
|
||||
func run(c *cli.Context) error {
|
||||
if c.Bool("init") {
|
||||
initConfig(c.Bool("force"), false, nil)
|
||||
initConfig(c.Bool("force"), false, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -350,7 +350,7 @@ func setupDebug() {
|
|||
//SetFacilityLogLevel("libp2p-raft", l)
|
||||
}
|
||||
|
||||
func initConfig(force bool, generateSecret bool, envSecret []byte) {
|
||||
func initConfig(force bool, generateSecret bool, envSecret string) {
|
||||
if _, err := os.Stat(configPath); err == nil && !force {
|
||||
err := fmt.Errorf("%s exists. Try running with -f", configPath)
|
||||
checkErr("", err)
|
||||
|
@ -363,11 +363,12 @@ func initConfig(force bool, generateSecret bool, envSecret []byte) {
|
|||
if len(envSecret) != 0 {
|
||||
// read cluster secret from env variable
|
||||
fmt.Println("Reading cluster secret from CLUSTER_SECRET environment variable.")
|
||||
cfg.ClusterSecret = []byte(envSecret)
|
||||
cfg.ClusterSecret, err = ipfscluster.DecodeClusterSecret(envSecret)
|
||||
} else {
|
||||
// get cluster secret from user
|
||||
cfg.ClusterSecret = []byte(promptUser("Enter cluster secret (to automatically generate, rerun with --gen-secret): "))
|
||||
cfg.ClusterSecret, err = ipfscluster.DecodeClusterSecret(promptUser("Enter cluster secret (to automatically generate, rerun with --gen-secret): "))
|
||||
}
|
||||
checkErr("parsing cluster secret", err)
|
||||
}
|
||||
|
||||
cfg.ConsensusDataFolder = dataPath
|
||||
|
|
|
@ -27,7 +27,7 @@ func TestSimplePNet(t *testing.T) {
|
|||
func TestClusterSecretRequired(t *testing.T) {
|
||||
cl1Secret, err := generateClusterSecret()
|
||||
if err != nil {
|
||||
t.Fatal("Unable to generate cluster secret")
|
||||
t.Fatal("Unable to generate cluster secret.")
|
||||
}
|
||||
cl1, _ := createOnePeerCluster(t, 1, cl1Secret)
|
||||
cl2, _ := createOnePeerCluster(t, 2, testingClusterSecret)
|
||||
|
|
Loading…
Reference in New Issue
Block a user