mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 22:56:43 +00:00
node,rpc: adds TLS support
This commit is contained in:
parent
891fcd8ce1
commit
d8ee45a0aa
10 changed files with 332 additions and 9 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -108,7 +109,8 @@ func localConsole(ctx *cli.Context) error {
|
||||||
// console to it.
|
// console to it.
|
||||||
func remoteConsole(ctx *cli.Context) error {
|
func remoteConsole(ctx *cli.Context) error {
|
||||||
// Attach to a remotely running geth instance and start the JavaScript console
|
// Attach to a remotely running geth instance and start the JavaScript console
|
||||||
client, err := dialRPC(ctx.Args().First())
|
dialctx := rpc.MakeTLSConfigContext(utils.MakeClientTLSConfig(ctx))
|
||||||
|
client, err := dialRPCContext(dialctx, ctx.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Unable to attach to remote geth: %v", err)
|
utils.Fatalf("Unable to attach to remote geth: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -137,9 +139,14 @@ func remoteConsole(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// dialRPC returns a RPC client which connects to the given endpoint.
|
// dialRPC returns a RPC client which connects to the given endpoint.
|
||||||
|
func dialRPC(endpoint string) (*rpc.Client, error) {
|
||||||
|
return dialRPCContext(context.Background(), endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialRPCContext returns a RPC client which connects to the given endpoint.
|
||||||
// The check for empty endpoint implements the defaulting logic
|
// The check for empty endpoint implements the defaulting logic
|
||||||
// for "geth attach" and "geth monitor" with no argument.
|
// for "geth attach" and "geth monitor" with no argument.
|
||||||
func dialRPC(endpoint string) (*rpc.Client, error) {
|
func dialRPCContext(ctx context.Context, endpoint string) (*rpc.Client, error) {
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
endpoint = node.DefaultIPCEndpoint(clientIdentifier)
|
endpoint = node.DefaultIPCEndpoint(clientIdentifier)
|
||||||
} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
|
} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
|
||||||
|
|
@ -147,7 +154,7 @@ func dialRPC(endpoint string) (*rpc.Client, error) {
|
||||||
// these prefixes.
|
// these prefixes.
|
||||||
endpoint = endpoint[4:]
|
endpoint = endpoint[4:]
|
||||||
}
|
}
|
||||||
return rpc.Dial(endpoint)
|
return rpc.DialContext(ctx, endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
|
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,11 @@ func init() {
|
||||||
utils.RPCListenAddrFlag,
|
utils.RPCListenAddrFlag,
|
||||||
utils.RPCPortFlag,
|
utils.RPCPortFlag,
|
||||||
utils.RPCApiFlag,
|
utils.RPCApiFlag,
|
||||||
|
utils.TLSEnabledFlag,
|
||||||
|
utils.TLSCertFileFlag,
|
||||||
|
utils.TLSCertCAFlag,
|
||||||
|
utils.TLSKeyFileFlag,
|
||||||
|
utils.TLSNoVerifyFlag,
|
||||||
utils.WSEnabledFlag,
|
utils.WSEnabledFlag,
|
||||||
utils.WSListenAddrFlag,
|
utils.WSListenAddrFlag,
|
||||||
utils.WSPortFlag,
|
utils.WSPortFlag,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
|
@ -75,7 +76,7 @@ func monitor(ctx *cli.Context) error {
|
||||||
)
|
)
|
||||||
// Attach to an Ethereum node over IPC or RPC
|
// Attach to an Ethereum node over IPC or RPC
|
||||||
endpoint := ctx.String(monitorCommandAttachFlag.Name)
|
endpoint := ctx.String(monitorCommandAttachFlag.Name)
|
||||||
if client, err = dialRPC(endpoint); err != nil {
|
if client, err = dialRPCContext(context.Background(), endpoint); err != nil {
|
||||||
utils.Fatalf("Unable to attach to geth node: %v", err)
|
utils.Fatalf("Unable to attach to geth node: %v", err)
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,11 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.RPCListenAddrFlag,
|
utils.RPCListenAddrFlag,
|
||||||
utils.RPCPortFlag,
|
utils.RPCPortFlag,
|
||||||
utils.RPCApiFlag,
|
utils.RPCApiFlag,
|
||||||
|
utils.TLSEnabledFlag,
|
||||||
|
utils.TLSCertFileFlag,
|
||||||
|
utils.TLSCertCAFlag,
|
||||||
|
utils.TLSKeyFileFlag,
|
||||||
|
utils.TLSNoVerifyFlag,
|
||||||
utils.WSEnabledFlag,
|
utils.WSEnabledFlag,
|
||||||
utils.WSListenAddrFlag,
|
utils.WSListenAddrFlag,
|
||||||
utils.WSPortFlag,
|
utils.WSPortFlag,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
|
|
@ -274,6 +275,26 @@ var (
|
||||||
Usage: "API's offered over the HTTP-RPC interface",
|
Usage: "API's offered over the HTTP-RPC interface",
|
||||||
Value: rpc.DefaultHTTPApis,
|
Value: rpc.DefaultHTTPApis,
|
||||||
}
|
}
|
||||||
|
TLSEnabledFlag = cli.BoolFlag{
|
||||||
|
Name: "tls",
|
||||||
|
Usage: "Activate TLS support on HTTP-RPC server",
|
||||||
|
}
|
||||||
|
TLSCertFileFlag = cli.StringFlag{
|
||||||
|
Name: "tlscert",
|
||||||
|
Usage: "TLS certificate file (auto-generated if empty, and TLS support is enabled)",
|
||||||
|
}
|
||||||
|
TLSCertCAFlag = cli.BoolFlag{
|
||||||
|
Name: "tlscertca",
|
||||||
|
Usage: "Whether provided certificate is serves as its own CA",
|
||||||
|
}
|
||||||
|
TLSKeyFileFlag = cli.StringFlag{
|
||||||
|
Name: "tlskey",
|
||||||
|
Usage: "TLS key file (auto-generated if empty, and TLS support is enabled)",
|
||||||
|
}
|
||||||
|
TLSNoVerifyFlag = cli.BoolFlag{
|
||||||
|
Name: "tlsnoverify",
|
||||||
|
Usage: "Whether server's TSL certificate must be verified or not (on connection by client)",
|
||||||
|
}
|
||||||
IPCDisabledFlag = cli.BoolFlag{
|
IPCDisabledFlag = cli.BoolFlag{
|
||||||
Name: "ipcdisable",
|
Name: "ipcdisable",
|
||||||
Usage: "Disable the IPC-RPC server",
|
Usage: "Disable the IPC-RPC server",
|
||||||
|
|
@ -687,6 +708,7 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
|
||||||
WSPort: ctx.GlobalInt(WSPortFlag.Name),
|
WSPort: ctx.GlobalInt(WSPortFlag.Name),
|
||||||
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
|
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
|
||||||
WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)),
|
WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)),
|
||||||
|
TLSConfig: MakeServerTLSConfig(ctx),
|
||||||
}
|
}
|
||||||
if ctx.GlobalBool(DevModeFlag.Name) {
|
if ctx.GlobalBool(DevModeFlag.Name) {
|
||||||
if !ctx.GlobalIsSet(DataDirFlag.Name) {
|
if !ctx.GlobalIsSet(DataDirFlag.Name) {
|
||||||
|
|
@ -703,6 +725,9 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
|
||||||
}
|
}
|
||||||
config.NetRestrict = list
|
config.NetRestrict = list
|
||||||
}
|
}
|
||||||
|
if config.TLSConfig != nil {
|
||||||
|
config.TLSEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
stack, err := node.New(config)
|
stack, err := node.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -958,3 +983,56 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
|
||||||
}
|
}
|
||||||
return preloads
|
return preloads
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeServerTLSConfig parses incoming TLS-related options, and produces configuration
|
||||||
|
// ready to be injected into server connection's transport
|
||||||
|
func MakeServerTLSConfig(ctx *cli.Context) *tls.Config {
|
||||||
|
if !ctx.GlobalBool(RPCEnabledFlag.Name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !ctx.GlobalBool(TLSEnabledFlag.Name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := rpc.MakeServerTLSConfig(MakeHTTPRpcHost(ctx), MakeTLSCertPath(ctx), MakeTLSKeyPath(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeClientTLSConfig parses incoming TLS-related options, and produces configuration
|
||||||
|
// ready to be injected into client connection's transport
|
||||||
|
func MakeClientTLSConfig(ctx *cli.Context) *tls.Config {
|
||||||
|
return rpc.MakeClientTLSConfig(
|
||||||
|
MakeTLSCertPath(ctx), MakeTLSKeyPath(ctx),
|
||||||
|
ctx.GlobalBool(TLSCertCAFlag.Name),
|
||||||
|
ctx.GlobalBool(TLSNoVerifyFlag.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeTLSCertPath returns TLS certificate file path from CLI options
|
||||||
|
// (or default one, if nothing is provided in options)
|
||||||
|
func MakeTLSCertPath(ctx *cli.Context) string {
|
||||||
|
basePath := MakeDataDir(ctx)
|
||||||
|
|
||||||
|
certPath := ctx.GlobalString(TLSCertFileFlag.Name)
|
||||||
|
if certPath == "" {
|
||||||
|
certPath = filepath.Join(basePath, rpc.DefaultTLSCertFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeTLSKeyPath returns TLS key file path from CLI options
|
||||||
|
// (or default one, if nothing is provided in options)
|
||||||
|
func MakeTLSKeyPath(ctx *cli.Context) string {
|
||||||
|
basePath := MakeDataDir(ctx)
|
||||||
|
|
||||||
|
keyPath := ctx.GlobalString(TLSKeyFileFlag.Name)
|
||||||
|
if keyPath == "" {
|
||||||
|
keyPath = filepath.Join(basePath, rpc.DefaultTLSKeyFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPath
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -157,6 +158,12 @@ type Config struct {
|
||||||
// exposed.
|
// exposed.
|
||||||
HTTPModules []string
|
HTTPModules []string
|
||||||
|
|
||||||
|
// TLSEnabled whether TLSConfig was correctly populated, and server is TLS-enabled node.
|
||||||
|
TLSEnabled bool
|
||||||
|
|
||||||
|
// TLSConfig is used to configure TLS capability of a server
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
|
||||||
// WSHost is the host interface on which to start the websocket RPC server. If
|
// WSHost is the host interface on which to start the websocket RPC server. If
|
||||||
// this field is empty, no websocket API endpoint will be started.
|
// this field is empty, no websocket API endpoint will be started.
|
||||||
WSHost string
|
WSHost string
|
||||||
|
|
|
||||||
14
node/node.go
14
node/node.go
|
|
@ -17,6 +17,7 @@
|
||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -407,8 +408,17 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors
|
||||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if n.config.TLSEnabled {
|
||||||
|
listener = tls.NewListener(listener, n.config.TLSConfig)
|
||||||
|
}
|
||||||
|
|
||||||
go rpc.NewHTTPServer(cors, handler).Serve(listener)
|
go rpc.NewHTTPServer(cors, handler).Serve(listener)
|
||||||
|
if n.config.TLSEnabled {
|
||||||
|
glog.V(logger.Info).Infof("HTTPS endpoint opened: https://%s", endpoint)
|
||||||
|
} else {
|
||||||
glog.V(logger.Info).Infof("HTTP endpoint opened: http://%s", endpoint)
|
glog.V(logger.Info).Infof("HTTP endpoint opened: http://%s", endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
// All listeners booted successfully
|
// All listeners booted successfully
|
||||||
n.httpEndpoint = endpoint
|
n.httpEndpoint = endpoint
|
||||||
|
|
@ -424,8 +434,12 @@ func (n *Node) stopHTTP() {
|
||||||
n.httpListener.Close()
|
n.httpListener.Close()
|
||||||
n.httpListener = nil
|
n.httpListener = nil
|
||||||
|
|
||||||
|
if n.config.TLSEnabled {
|
||||||
|
glog.V(logger.Info).Infof("HTTPS endpoint closed: https://%s", n.httpEndpoint)
|
||||||
|
} else {
|
||||||
glog.V(logger.Info).Infof("HTTP endpoint closed: http://%s", n.httpEndpoint)
|
glog.V(logger.Info).Infof("HTTP endpoint closed: http://%s", n.httpEndpoint)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if n.httpHandler != nil {
|
if n.httpHandler != nil {
|
||||||
n.httpHandler.Stop()
|
n.httpHandler.Stop()
|
||||||
n.httpHandler = nil
|
n.httpHandler = nil
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) {
|
||||||
}
|
}
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
return DialHTTP(rawurl)
|
return DialHTTP(ctx, rawurl)
|
||||||
case "ws", "wss":
|
case "ws", "wss":
|
||||||
return DialWebsocket(ctx, rawurl, "")
|
return DialWebsocket(ctx, rawurl, "")
|
||||||
case "":
|
case "":
|
||||||
|
|
|
||||||
11
rpc/http.go
11
rpc/http.go
|
|
@ -64,7 +64,7 @@ func (hc *httpConn) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialHTTP creates a new RPC clients that connection to an RPC server over HTTP.
|
// DialHTTP creates a new RPC clients that connection to an RPC server over HTTP.
|
||||||
func DialHTTP(endpoint string) (*Client, error) {
|
func DialHTTP(ctx context.Context, endpoint string) (*Client, error) {
|
||||||
req, err := http.NewRequest("POST", endpoint, nil)
|
req, err := http.NewRequest("POST", endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -74,7 +74,14 @@ func DialHTTP(endpoint string) (*Client, error) {
|
||||||
|
|
||||||
initctx := context.Background()
|
initctx := context.Background()
|
||||||
return newClient(initctx, func(context.Context) (net.Conn, error) {
|
return newClient(initctx, func(context.Context) (net.Conn, error) {
|
||||||
return &httpConn{client: new(http.Client), req: req, closed: make(chan struct{})}, nil
|
// configure client capable of TLS handling
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: TLSConfigFromContext(ctx),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpConn{client: client, req: req, closed: make(chan struct{})}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
199
rpc/tls.go
Normal file
199
rpc/tls.go
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSConfigKey is used to store a TLS configuration within the connection context
|
||||||
|
type TLSConfigKey struct{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultTLSCertFile = "tlscert.pem"
|
||||||
|
DefaultTLSKeyFile = "tlskey.pem"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validFor = 365 * 24 * time.Hour
|
||||||
|
rsaBits = 2048
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSConfigFromContext fetches TLS configuration from context.
|
||||||
|
// Fetched config is to be normally used to configure client connection's transport.
|
||||||
|
func TLSConfigFromContext(ctx context.Context) *tls.Config {
|
||||||
|
config, ok := ctx.Value(TLSConfigKey{}).(*tls.Config)
|
||||||
|
if !ok {
|
||||||
|
return &tls.Config{}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeTLSContext packs TLS configuration into context.
|
||||||
|
func MakeTLSConfigContext(config *tls.Config) context.Context {
|
||||||
|
return context.WithValue(context.Background(), TLSConfigKey{}, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeServerTLSConfig(host string, certPath string, keyPath string) (*tls.Config, error) {
|
||||||
|
config := &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||||
|
PreferServerCipherSuites: true,
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure certificate and key files do exist
|
||||||
|
if err := EnsureCerts(certPath, keyPath, host); err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load certificate/key
|
||||||
|
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
|
if err == nil {
|
||||||
|
config.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeClientTLSConfig is used to configure connection context. Particularly,
|
||||||
|
// setting up TLS support for connection transport.
|
||||||
|
//
|
||||||
|
// For auto-generated certificates, isCA is always true, that's generated
|
||||||
|
// certificate is used as its own parent when signing.
|
||||||
|
func MakeClientTLSConfig(certPath string, keyPath string, isCA bool, certNoVerify bool) *tls.Config {
|
||||||
|
config := &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||||
|
PreferServerCipherSuites: true,
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load certificate/key
|
||||||
|
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
|
if err == nil {
|
||||||
|
config.Certificates = []tls.Certificate{cert}
|
||||||
|
|
||||||
|
// Provide CA certs
|
||||||
|
if isCA {
|
||||||
|
if rootCert, err := ioutil.ReadFile(certPath); err == nil {
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
certPool.AppendCertsFromPEM(rootCert)
|
||||||
|
|
||||||
|
config.RootCAs = certPool
|
||||||
|
config.ClientCAs = certPool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow to skip TLS certificate verification
|
||||||
|
if certNoVerify {
|
||||||
|
config.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureCerts check certificate/key files, and auto-generates both if necessary
|
||||||
|
func EnsureCerts(certPath string, keyPath string, host string) error {
|
||||||
|
var certFileExists, keyFileExists bool
|
||||||
|
|
||||||
|
if _, err := os.Stat(certPath); os.IsNotExist(err) {
|
||||||
|
certFileExists = false
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||||
|
keyFileExists = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate cert and private key (if they are not already available)
|
||||||
|
if !certFileExists || !keyFileExists {
|
||||||
|
if err := Generate(certPath, keyPath, host); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Generate(certPath string, keyPath string, host string) error {
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate private key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
notBefore := time.Now()
|
||||||
|
notAfter := notBefore.Add(validFor)
|
||||||
|
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate serial number: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"Acme Co"},
|
||||||
|
},
|
||||||
|
NotBefore: notBefore,
|
||||||
|
NotAfter: notAfter,
|
||||||
|
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
template.IPAddresses = append(template.IPAddresses, ip)
|
||||||
|
} else {
|
||||||
|
template.DNSNames = append(template.DNSNames, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
template.IsCA = true
|
||||||
|
template.KeyUsage |= x509.KeyUsageCertSign
|
||||||
|
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to create certificate: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certOut, err := os.Create(certPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open "+certPath+" for writing: %s", err)
|
||||||
|
}
|
||||||
|
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||||
|
certOut.Close()
|
||||||
|
glog.V(logger.Info).Infof("TLS: %s written", certPath)
|
||||||
|
|
||||||
|
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open "+keyPath+" for writing: %s", err)
|
||||||
|
}
|
||||||
|
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||||
|
keyOut.Close()
|
||||||
|
glog.V(logger.Info).Infof("TLS: %s written", keyPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue