Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reject connections based on CRL file #8124

Merged
merged 5 commits into from
Jun 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions e2e/ctl_v3_kv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package e2e

import (
"fmt"
"strings"
"testing"
)

Expand Down Expand Up @@ -49,6 +50,29 @@ func TestCtlV3DelClientTLS(t *testing.T) { testCtl(t, delTest, withCfg(configCli
func TestCtlV3DelPeerTLS(t *testing.T) { testCtl(t, delTest, withCfg(configPeerTLS)) }
func TestCtlV3DelTimeout(t *testing.T) { testCtl(t, delTest, withDialTimeout(0)) }

func TestCtlV3GetRevokedCRL(t *testing.T) {
cfg := etcdProcessClusterConfig{
clusterSize: 1,
initialToken: "new",
clientTLS: clientTLS,
isClientCRL: true,
clientCertAuthEnabled: true,
}
testCtl(t, testGetRevokedCRL, withCfg(cfg))
}

func testGetRevokedCRL(cx ctlCtx) {
// test reject
if err := ctlV3Put(cx, "k", "v", ""); err == nil || !strings.Contains(err.Error(), "code = Internal") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code = Internal

Is this from grpc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes; the error changes depending where grpc is at the time of the server killing the connection, but it's always Internal

cx.t.Fatalf("expected reset connection, got %v", err)
}
// test accept
cx.epc.cfg.isClientCRL = false
if err := ctlV3Put(cx, "k", "v", ""); err != nil {
cx.t.Fatal(err)
}
}

func putTest(cx ctlCtx) {
key, value := "foo", "bar"

Expand Down
4 changes: 4 additions & 0 deletions e2e/ctl_v3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ func (cx *ctlCtx) prefixArgs(eps []string) []string {
if cx.epc.cfg.isClientAutoTLS {
fmap["insecure-transport"] = "false"
fmap["insecure-skip-tls-verify"] = "true"
} else if cx.epc.cfg.isClientCRL {
fmap["cacert"] = caPath
fmap["cert"] = revokedCertPath
fmap["key"] = revokedPrivateKeyPath
} else {
fmap["cacert"] = caPath
fmap["cert"] = certPath
Expand Down
23 changes: 19 additions & 4 deletions e2e/etcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ var (
certPath string
privateKeyPath string
caPath string

crlPath string
revokedCertPath string
revokedPrivateKeyPath string
)

type clientConnType int
Expand Down Expand Up @@ -175,10 +179,12 @@ type etcdProcessClusterConfig struct {
isPeerTLS bool
isPeerAutoTLS bool
isClientAutoTLS bool
forceNewCluster bool
initialToken string
quotaBackendBytes int64
noStrictReconfig bool
isClientCRL bool

forceNewCluster bool
initialToken string
quotaBackendBytes int64
noStrictReconfig bool
}

// newEtcdProcessCluster launches a new cluster from etcd processes, returning
Expand Down Expand Up @@ -228,6 +234,10 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig {
privateKeyPath = certDir + "/server.key.insecure"
caPath = certDir + "/ca.crt"

revokedCertPath = certDir + "/server-revoked.crt"
revokedPrivateKeyPath = certDir + "/server-revoked.key.insecure"
crlPath = certDir + "/revoke.crl"

if cfg.basePort == 0 {
cfg.basePort = etcdProcessBasePort
}
Expand Down Expand Up @@ -384,6 +394,11 @@ func (cfg *etcdProcessClusterConfig) tlsArgs() (args []string) {
args = append(args, tlsPeerArgs...)
}
}

if cfg.isClientCRL {
args = append(args, "--client-crl-file", crlPath, "--client-cert-auth")
}

return args
}

Expand Down
7 changes: 1 addition & 6 deletions embed/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package embed

import (
"context"
"crypto/tls"
"fmt"
"io/ioutil"
defaultLog "log"
Expand Down Expand Up @@ -365,12 +364,8 @@ func startClientListeners(cfg *Config) (sctxs map[string]*serveCtx, err error) {
}

func (e *Etcd) serve() (err error) {
var ctlscfg *tls.Config
if !e.cfg.ClientTLSInfo.Empty() {
plog.Infof("ClientTLS: %s", e.cfg.ClientTLSInfo)
if ctlscfg, err = e.cfg.ClientTLSInfo.ServerConfig(); err != nil {
return err
}
}

if e.cfg.CorsInfo.String() != "" {
Expand All @@ -394,7 +389,7 @@ func (e *Etcd) serve() (err error) {
}
for _, sctx := range e.sctxs {
go func(s *serveCtx) {
e.errHandler(s.serve(e.Server, ctlscfg, v2h, e.errHandler))
e.errHandler(s.serve(e.Server, &e.cfg.ClientTLSInfo, v2h, e.errHandler))
}(sctx)
}
return nil
Expand Down
13 changes: 10 additions & 3 deletions embed/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package embed

import (
"crypto/tls"
"io/ioutil"
defaultLog "log"
"net"
Expand All @@ -33,6 +32,7 @@ import (
"github.com/coreos/etcd/etcdserver/api/v3rpc"
etcdservergw "github.com/coreos/etcd/etcdserver/etcdserverpb/gw"
"github.com/coreos/etcd/pkg/debugutil"
"github.com/coreos/etcd/pkg/transport"

"github.com/cockroachdb/cmux"
gw "github.com/grpc-ecosystem/grpc-gateway/runtime"
Expand Down Expand Up @@ -65,7 +65,7 @@ func newServeCtx() *serveCtx {
// serve accepts incoming connections on the listener l,
// creating a new service goroutine for each. The service goroutines
// read requests and then call handler to reply to them.
func (sctx *serveCtx) serve(s *etcdserver.EtcdServer, tlscfg *tls.Config, handler http.Handler, errHandler func(error)) error {
func (sctx *serveCtx) serve(s *etcdserver.EtcdServer, tlsinfo *transport.TLSInfo, handler http.Handler, errHandler func(error)) error {
logger := defaultLog.New(ioutil.Discard, "etcdhttp", 0)
<-s.ReadyNotify()
plog.Info("ready to serve client requests")
Expand Down Expand Up @@ -106,6 +106,10 @@ func (sctx *serveCtx) serve(s *etcdserver.EtcdServer, tlscfg *tls.Config, handle
}

if sctx.secure {
tlscfg, tlsErr := tlsinfo.ServerConfig()
if tlsErr != nil {
return tlsErr
}
gs := v3rpc.Server(s, tlscfg)
sctx.grpcServerC <- gs
v3electionpb.RegisterElectionServer(gs, servElection)
Expand All @@ -125,7 +129,10 @@ func (sctx *serveCtx) serve(s *etcdserver.EtcdServer, tlscfg *tls.Config, handle
return err
}

tlsl := tls.NewListener(m.Match(cmux.Any()), tlscfg)
tlsl, lerr := transport.NewTLSListener(m.Match(cmux.Any()), tlsinfo)
if lerr != nil {
return lerr
}
// TODO: add debug flag; enable logging when debug flag is set
httpmux := sctx.createMux(gwmux, handler)

Expand Down
2 changes: 2 additions & 0 deletions etcdmain/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ func newConfig() *config {
fs.StringVar(&cfg.ClientTLSInfo.CertFile, "cert-file", "", "Path to the client server TLS cert file.")
fs.StringVar(&cfg.ClientTLSInfo.KeyFile, "key-file", "", "Path to the client server TLS key file.")
fs.BoolVar(&cfg.ClientTLSInfo.ClientCertAuth, "client-cert-auth", false, "Enable client cert authentication.")
fs.StringVar(&cfg.ClientTLSInfo.CRLFile, "client-crl-file", "", "Path to the client certificate revocation list file.")
fs.StringVar(&cfg.ClientTLSInfo.TrustedCAFile, "trusted-ca-file", "", "Path to the client server TLS trusted CA key file.")
fs.BoolVar(&cfg.ClientAutoTLS, "auto-tls", false, "Client TLS using generated certificates")
fs.StringVar(&cfg.PeerTLSInfo.CAFile, "peer-ca-file", "", "DEPRECATED: Path to the peer server TLS CA file.")
Expand All @@ -187,6 +188,7 @@ func newConfig() *config {
fs.BoolVar(&cfg.PeerTLSInfo.ClientCertAuth, "peer-client-cert-auth", false, "Enable peer client cert authentication.")
fs.StringVar(&cfg.PeerTLSInfo.TrustedCAFile, "peer-trusted-ca-file", "", "Path to the peer server TLS trusted CA file.")
fs.BoolVar(&cfg.PeerAutoTLS, "peer-auto-tls", false, "Peer TLS using generated certificates")
fs.StringVar(&cfg.PeerTLSInfo.CRLFile, "peer-crl-file", "", "Path to the peer certificate revocation list file.")

// logging
fs.BoolVar(&cfg.Debug, "debug", false, "Enable debug-level logging for etcd.")
Expand Down
4 changes: 4 additions & 0 deletions etcdmain/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ security flags:
path to the client server TLS key file.
--client-cert-auth 'false'
enable client cert authentication.
--client-crl-file ''
path to the client certificate revocation list file.
--trusted-ca-file ''
path to the client server TLS trusted CA key file.
--auto-tls 'false'
Expand All @@ -144,6 +146,8 @@ security flags:
path to the peer server TLS trusted CA file.
--peer-auto-tls 'false'
peer TLS using self-generated certificates if --peer-key-file and --peer-cert-file are not provided.
--peer-crl-file ''
path to the peer certificate revocation list file.

logging flags

Expand Down
19 changes: 19 additions & 0 deletions integration/fixtures/ca-csr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"key": {
"algo": "rsa",
"size": 4096
},
"names": [
{
"O": "etcd",
"OU": "etcd Security",
"L": "San Francisco",
"ST": "California",
"C": "USA"
}
],
"CN": "ca",
"ca": {
"expiry": "87600h"
}
}
52 changes: 31 additions & 21 deletions integration/fixtures/ca.crt
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
-----BEGIN CERTIFICATE-----
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
zceob2+4yKU2W+OQNeVChUzc
-----END CERTIFICATE-----
13 changes: 13 additions & 0 deletions integration/fixtures/gencert.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"signing": {
"default": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "87600h"
}
}
}
34 changes: 34 additions & 0 deletions integration/fixtures/gencerts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash

if ! [[ "$0" =~ "./gencerts.sh" ]]; then
echo "must be run from 'fixtures'"
exit 255
fi

if ! which cfssl; then
echo "cfssl is not installed"
exit 255
fi

cfssl gencert --initca=true ./ca-csr.json | cfssljson --bare ./ca
mv ca.pem ca.crt

cfssl gencert \
--ca ./ca.crt \
--ca-key ./ca-key.pem \
--config ./gencert.json \
./server-ca-csr.json | cfssljson --bare ./server
mv server.pem server.crt
mv server-key.pem server.key.insecure

cfssl gencert --ca ./ca.crt \
--ca-key ./ca-key.pem \
--config ./gencert.json \
./server-ca-csr.json 2>revoked.stderr | cfssljson --bare ./server-revoked
mv server-revoked.pem server-revoked.crt
mv server-revoked-key.pem server-revoked.key.insecure

grep serial revoked.stderr | awk ' { print $9 } ' >revoke.txt
cfssl gencrl revoke.txt ca.crt ca-key.pem | base64 -d >revoke.crl

rm -f *.csr *.pem *.stderr *.txt
Binary file added integration/fixtures/revoke.crl
Binary file not shown.
20 changes: 20 additions & 0 deletions integration/fixtures/server-ca-csr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"key": {
"algo": "rsa",
"size": 4096
},
"names": [
{
"O": "etcd",
"OU": "etcd Security",
"L": "San Francisco",
"ST": "California",
"C": "USA"
}
],
"CN": "example.com",
"hosts": [
"127.0.0.1",
"localhost"
]
}
35 changes: 35 additions & 0 deletions integration/fixtures/server-revoked.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
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
mdixLrkAUlyB50yoiYjbdTvFzvw40A==
-----END CERTIFICATE-----
Loading