Skip to content

Commit

Permalink
rpcserver: Adds ability to allow alternative dns names for TLS.
Browse files Browse the repository at this point in the history
- rpcserver: Adds ability to allow alternative dns names for TLS.
- Adds AltDNSNames config for passing additional hostnames to the
  TLS certificate generate method.  Without this change there is no
  way to pass in alternative dns names for the rpc server.
- Additionally this commit allows the user to set 'DCRD_ALT_DNSNAMES'
  as an environment variable when starting dcrd.  This is useful for
  docker and other app runtimes that require apps to be 12 factor.
  • Loading branch information
logicminds authored and davecgh committed Oct 11, 2018
1 parent 8c588a6 commit ea54d03
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 3 deletions.
3 changes: 3 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ var (
defaultRPCKeyFile = filepath.Join(defaultHomeDir, "rpc.key")
defaultRPCCertFile = filepath.Join(defaultHomeDir, "rpc.cert")
defaultLogDir = filepath.Join(defaultHomeDir, defaultLogDirname)
defaultAltDNSNames = []string{}
)

// runServiceCommand is only set to a real function on Windows. It is used
Expand Down Expand Up @@ -166,6 +167,7 @@ type config struct {
PipeRx uint `long:"piperx" description:"File descriptor of read end pipe to enable parent -> child process communication"`
PipeTx uint `long:"pipetx" description:"File descriptor of write end pipe to enable parent <- child process communication"`
LifetimeEvents bool `long:"lifetimeevents" description:"Send lifetime notifications over the TX pipe"`
AltDNSNames []string `long:"altdnsnames" description:"Specify additional dns names to use when generating the rpc server certificate" env:"DCRD_ALT_DNSNAMES" env-delim:","`
onionlookup func(string) ([]net.IP, error)
lookup func(string) ([]net.IP, error)
oniondial func(string, string) (net.Conn, error)
Expand Down Expand Up @@ -457,6 +459,7 @@ func loadConfig() (*config, []string, error) {
AllowOldVotes: defaultAllowOldVotes,
NoExistsAddrIndex: defaultNoExistsAddrIndex,
NoCFilters: defaultNoCFilters,
AltDNSNames: defaultAltDNSNames,
}

// Service options which are only added on Windows.
Expand Down
68 changes: 68 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
"flag"
"io/ioutil"
"os"
"strings"
"testing"
)

// in order to test command line arguments and environment variables
// you will need to append the flags to the os.Args variable like so
// os.Args = append(os.Args, "--altdnsnames=\"hostname1,hostname2\"")
// For environment variables you can use the
// os.Setenv("DCRD_ALT_DNSNAMES", "hostname1,hostname2") to set the variable
// before loadConfig() is called
// These args and env variables will then get parsed by loadConfig()

func setup() {
// Temp config file is used to ensure there are no external influences
// from previously set env variables or default config files.
file, _ := ioutil.TempFile("", "dcrd_test_file.cfg")
defer os.Remove(file.Name())

// Parse the -test.* flags before removing them from the command line
// arguments list, which we do to allow go-flags to succeed.
flag.Parse()
os.Args = os.Args[:1]
}

func TestLoadConfig(t *testing.T) {
_, _, err := loadConfig()
if err != nil {
t.Errorf("Failed to load dcrd config: %s\n", err.Error())
}
}

func TestDefaultAltDNSNames(t *testing.T) {
cfg, _, _ := loadConfig()
if len(cfg.AltDNSNames) != 0 {
t.Errorf("Invalid default value for altdnsnames: %s\n", cfg.AltDNSNames)
}
}

func TestAltDNSNamesWithEnv(t *testing.T) {
os.Setenv("DCRD_ALT_DNSNAMES", "hostname1,hostname2")
cfg, _, _ := loadConfig()
hostnames := strings.Join(cfg.AltDNSNames, ",")
if hostnames != "hostname1,hostname2" {
t.Errorf("altDNSNames should be %s but was %s", "hostname1,hostname2", hostnames)
}
}

func TestAltDNSNamesWithArg(t *testing.T) {
setup()
old := os.Args
os.Args = append(os.Args, "--altdnsnames=\"hostname1,hostname2\"")
cfg, _, _ := loadConfig()
hostnames := strings.Join(cfg.AltDNSNames, ",")
if hostnames != "hostname1,hostname2" {
t.Errorf("altDNSNames should be %s but was %s", "hostname1,hostname2", hostnames)
}
os.Args = old
}
6 changes: 3 additions & 3 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6343,13 +6343,13 @@ func (s *rpcServer) Start() {
}

// genCertPair generates a key/cert pair to the paths provided.
func genCertPair(certFile, keyFile string) error {
func genCertPair(certFile, keyFile string, altDNSNames []string) error {
rpcsLog.Infof("Generating TLS certificates...")

org := "dcrd autogenerated cert"
validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
cert, key, err := certgen.NewTLSCertPair(elliptic.P521(), org,
validUntil, nil)
validUntil, altDNSNames)
if err != nil {
return err
}
Expand Down Expand Up @@ -6401,7 +6401,7 @@ func newRPCServer(listenAddrs []string, generator *BlkTmplGenerator, s *server)
// Generate the TLS cert and key file if both don't already
// exist.
if !fileExists(cfg.RPCKey) && !fileExists(cfg.RPCCert) {
err := genCertPair(cfg.RPCCert, cfg.RPCKey)
err := genCertPair(cfg.RPCCert, cfg.RPCKey, cfg.AltDNSNames)
if err != nil {
return nil, err
}
Expand Down
61 changes: 61 additions & 0 deletions rpcserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ package main

import (
"bytes"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"os"
"runtime/debug"
"testing"
Expand Down Expand Up @@ -106,6 +110,12 @@ var primaryHarness *rpctest.Harness
func TestMain(m *testing.M) {
var err error

// Parse the -test.* flags before removing them from the command line
// arguments list, which we do to allow go-flags to succeed.
// See config_test.go for more info
flag.Parse()
os.Args = os.Args[:1]

// In order to properly test scenarios on as if we were on mainnet,
// ensure that non-standard transactions aren't accepted into the
// mempool or relayed.
Expand Down Expand Up @@ -165,3 +175,54 @@ func TestRpcServer(t *testing.T) {
currentTestNum++
}
}

func TestCertCreationWithHosts(t *testing.T) {
certfile, err := ioutil.TempFile("", "certfile")
if err != nil {
t.Fatalf("Unable to create temp certfile: %s", err)
}
keyfile, err := ioutil.TempFile("", "keyfile")
if err != nil {
t.Fatalf("Unable to create temp keyfile: %s", err)
}
hostnames := []string{"hostname1", "hostname2"}
defer os.Remove(keyfile.Name())
defer os.Remove(certfile.Name())
err = genCertPair(certfile.Name(), keyfile.Name(), hostnames)
if err != nil {
t.Fatalf("certifcate was not created correctly: %s", err)
}
certBytes, err := ioutil.ReadFile(certfile.Name())
if err != nil {
t.Fatalf("Unable to read the certfile: %s", err)
}
pemCert, _ := pem.Decode(certBytes)
x509Cert, err := x509.ParseCertificate(pemCert.Bytes)
if err != nil {
t.Fatalf("Unable to parse the certificate: %s", err)
}
// Ensure the specified extra hosts are present.
for _, host := range hostnames {
err := x509Cert.VerifyHostname(host)
if err != nil {
t.Fatalf("failed to verify extra host '%s'", host)
}
}
}

func TestCertCreationWithOutHosts(t *testing.T) {
certfile, err := ioutil.TempFile("", "certfile")
if err != nil {
t.Fatalf("Unable to create temp certfile: %s", err)
}
keyfile, err := ioutil.TempFile("", "keyfile")
if err != nil {
t.Fatalf("Unable to create temp keyfile: %s", err)
}
defer os.Remove(keyfile.Name())
defer os.Remove(certfile.Name())
err = genCertPair(certfile.Name(), keyfile.Name(), []string{})
if err != nil {
t.Fatalf("certifcate was not created correctly: %s", err)
}
}

0 comments on commit ea54d03

Please sign in to comment.