etcd module: add support for ssl, better defaults, fix tests
This commit is contained in:
parent
54d3556e7a
commit
8256c07fc0
@ -28,13 +28,13 @@ in {
|
|||||||
|
|
||||||
listenClientUrls = mkOption {
|
listenClientUrls = mkOption {
|
||||||
description = "Etcd list of URLs to listen on for client traffic.";
|
description = "Etcd list of URLs to listen on for client traffic.";
|
||||||
default = ["http://localhost:4001"];
|
default = ["http://localhost:2379"];
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
};
|
};
|
||||||
|
|
||||||
listenPeerUrls = mkOption {
|
listenPeerUrls = mkOption {
|
||||||
description = "Etcd list of URLs to listen on for peer traffic.";
|
description = "Etcd list of URLs to listen on for peer traffic.";
|
||||||
default = ["http://localhost:7001"];
|
default = ["http://localhost:2380"];
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ in {
|
|||||||
|
|
||||||
initialCluster = mkOption {
|
initialCluster = mkOption {
|
||||||
description = "Etcd initial cluster configuration for bootstrapping.";
|
description = "Etcd initial cluster configuration for bootstrapping.";
|
||||||
default = ["${cfg.name}=http://localhost:7001"];
|
default = ["${cfg.name}=http://127.0.0.1:2380"];
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,6 +68,54 @@ in {
|
|||||||
type = types.str;
|
type = types.str;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
clientCertAuth = mkOption {
|
||||||
|
description = "Whether to use certs for client authentication";
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
trustedCaFile = mkOption {
|
||||||
|
description = "Certificate authority file to use for clients";
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
certFile = mkOption {
|
||||||
|
description = "Cert file to use for clients";
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
keyFile = mkOption {
|
||||||
|
description = "Key file to use for clients";
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
peerCertFile = mkOption {
|
||||||
|
description = "Cert file to use for peer to peer communication";
|
||||||
|
default = cfg.certFile;
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
peerKeyFile = mkOption {
|
||||||
|
description = "Key file to use for peer to peer communication";
|
||||||
|
default = cfg.keyFile;
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
peerTrustedCaFile = mkOption {
|
||||||
|
description = "Certificate authority file to use for peer to peer communication";
|
||||||
|
default = cfg.trustedCaFile;
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
peerClientCertAuth = mkOption {
|
||||||
|
description = "Whether to check all incoming peer requests from the cluster for valid client certificates signed by the supplied CA";
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
};
|
||||||
|
|
||||||
extraConf = mkOption {
|
extraConf = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Etcd extra configuration. See
|
Etcd extra configuration. See
|
||||||
@ -99,7 +147,7 @@ in {
|
|||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "network-interfaces.target" ];
|
after = [ "network-interfaces.target" ];
|
||||||
|
|
||||||
environment = {
|
environment = (filterAttrs (n: v: v != null) {
|
||||||
ETCD_NAME = cfg.name;
|
ETCD_NAME = cfg.name;
|
||||||
ETCD_DISCOVERY = cfg.discovery;
|
ETCD_DISCOVERY = cfg.discovery;
|
||||||
ETCD_DATA_DIR = cfg.dataDir;
|
ETCD_DATA_DIR = cfg.dataDir;
|
||||||
@ -107,7 +155,14 @@ in {
|
|||||||
ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
|
ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
|
||||||
ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
|
ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
|
||||||
ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
|
ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
|
||||||
} // (optionalAttrs (cfg.discovery == ""){
|
ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile;
|
||||||
|
ETCD_PEER_CERT_FILE = cfg.peerCertFile;
|
||||||
|
ETCD_PEER_KEY_FILE = cfg.peerKeyFile;
|
||||||
|
ETCD_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
|
||||||
|
ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile;
|
||||||
|
ETCD_CERT_FILE = cfg.certFile;
|
||||||
|
ETCD_KEY_FILE = cfg.keyFile;
|
||||||
|
}) // (optionalAttrs (cfg.discovery == ""){
|
||||||
ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster;
|
ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster;
|
||||||
ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState;
|
ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState;
|
||||||
ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken;
|
ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken;
|
||||||
|
@ -1,111 +1,128 @@
|
|||||||
# This test runs etcd as single node, multy node and using discovery
|
# This test runs simple etcd cluster
|
||||||
|
|
||||||
import ./make-test.nix ({ pkgs, ... } : {
|
import ./make-test.nix ({ pkgs, ... } : let
|
||||||
|
certs = pkgs.runCommand "certs" {
|
||||||
|
buildInputs = [pkgs.openssl];
|
||||||
|
} ''
|
||||||
|
mkdir -p $out
|
||||||
|
openssl genrsa -out $out/ca-key.pem 2048
|
||||||
|
openssl req -x509 -new -nodes -key $out/ca-key.pem -days 10000 -out $out/ca.pem -subj "/CN=etcd-ca"
|
||||||
|
|
||||||
|
cat << EOF > openssl.cnf
|
||||||
|
ions = v3_req
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
[req_distinguished_name]
|
||||||
|
[ v3_req ]
|
||||||
|
basicConstraints = CA:FALSE
|
||||||
|
keyUsage = digitalSignature, keyEncipherment
|
||||||
|
extendedKeyUsage = serverAuth
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = node1
|
||||||
|
DNS.2 = node2
|
||||||
|
DNS.3 = node3
|
||||||
|
IP.1 = 127.0.0.1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
openssl genrsa -out $out/etcd-key.pem 2048
|
||||||
|
openssl req -new -key $out/etcd-key.pem -out etcd.csr -subj "/CN=etcd" -config openssl.cnf
|
||||||
|
openssl x509 -req -in etcd.csr -CA $out/ca.pem -CAkey $out/ca-key.pem -CAcreateserial -out $out/etcd.pem -days 365 -extensions v3_req -extfile openssl.cnf
|
||||||
|
|
||||||
|
cat << EOF > client-openssl.cnf
|
||||||
|
ions = v3_req
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
[req_distinguished_name]
|
||||||
|
[ v3_req ]
|
||||||
|
basicConstraints = CA:FALSE
|
||||||
|
keyUsage = digitalSignature, keyEncipherment
|
||||||
|
extendedKeyUsage = clientAuth
|
||||||
|
EOF
|
||||||
|
|
||||||
|
openssl genrsa -out $out/etcd-client-key.pem 2048
|
||||||
|
openssl req -new -key $out/etcd-client-key.pem -out etcd-client.csr -subj "/CN=etcd-client" -config client-openssl.cnf
|
||||||
|
openssl x509 -req -in etcd-client.csr -CA $out/ca.pem -CAkey $out/ca-key.pem -CAcreateserial -out $out/etcd-client.pem -days 365 -extensions v3_req -extfile client-openssl.cnf
|
||||||
|
'';
|
||||||
|
|
||||||
|
nodeConfig = {
|
||||||
|
services = {
|
||||||
|
etcd = {
|
||||||
|
enable = true;
|
||||||
|
keyFile = "${certs}/etcd-key.pem";
|
||||||
|
certFile = "${certs}/etcd.pem";
|
||||||
|
trustedCaFile = "${certs}/ca.pem";
|
||||||
|
peerClientCertAuth = true;
|
||||||
|
listenClientUrls = ["https://127.0.0.1:2379"];
|
||||||
|
listenPeerUrls = ["https://0.0.0.0:2380"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.variables = {
|
||||||
|
ETCDCTL_CERT_FILE = "${certs}/etcd-client.pem";
|
||||||
|
ETCDCTL_KEY_FILE = "${certs}/etcd-client-key.pem";
|
||||||
|
ETCDCTL_CA_FILE = "${certs}/ca.pem";
|
||||||
|
ETCDCTL_PEERS = "https://127.0.0.1:2379";
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 2380 ];
|
||||||
|
};
|
||||||
|
in {
|
||||||
name = "etcd";
|
name = "etcd";
|
||||||
|
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
maintainers = [ offline ];
|
maintainers = [ offline ];
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
simple =
|
node1 = { config, pkgs, nodes, ... }: {
|
||||||
{ config, pkgs, nodes, ... }:
|
require = [nodeConfig];
|
||||||
{
|
services.etcd = {
|
||||||
services.etcd.enable = true;
|
initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
|
||||||
services.etcd.listenClientUrls = ["http://0.0.0.0:4001"];
|
initialAdvertisePeerUrls = ["https://node1:2380"];
|
||||||
environment.systemPackages = [ pkgs.curl ];
|
};
|
||||||
networking.firewall.allowedTCPPorts = [ 4001 ];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
node1 =
|
|
||||||
{ config, pkgs, nodes, ... }:
|
|
||||||
{
|
|
||||||
services = {
|
|
||||||
etcd = {
|
|
||||||
enable = true;
|
|
||||||
listenPeerUrls = ["http://0.0.0.0:7001"];
|
|
||||||
initialAdvertisePeerUrls = ["http://node1:7001"];
|
|
||||||
initialCluster = ["node1=http://node1:7001" "node2=http://node2:7001"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 7001 ];
|
|
||||||
};
|
|
||||||
|
|
||||||
node2 =
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
{
|
|
||||||
services = {
|
|
||||||
etcd = {
|
|
||||||
enable = true;
|
|
||||||
listenPeerUrls = ["http://0.0.0.0:7001"];
|
|
||||||
initialAdvertisePeerUrls = ["http://node2:7001"];
|
|
||||||
initialCluster = ["node1=http://node1:7001" "node2=http://node2:7001"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 7001 ];
|
|
||||||
};
|
|
||||||
|
|
||||||
discovery1 =
|
|
||||||
{ config, pkgs, nodes, ... }:
|
|
||||||
{
|
|
||||||
services = {
|
|
||||||
etcd = {
|
|
||||||
enable = true;
|
|
||||||
listenPeerUrls = ["http://0.0.0.0:7001"];
|
|
||||||
initialAdvertisePeerUrls = ["http://discovery1:7001"];
|
|
||||||
discovery = "http://simple:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 7001 ];
|
|
||||||
};
|
|
||||||
|
|
||||||
discovery2 =
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
{
|
|
||||||
services = {
|
|
||||||
etcd = {
|
|
||||||
enable = true;
|
|
||||||
listenPeerUrls = ["http://0.0.0.0:7001"];
|
|
||||||
initialAdvertisePeerUrls = ["http://discovery2:7001"];
|
|
||||||
discovery = "http://simple:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 7001 ];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
node2 = { config, pkgs, ... }: {
|
||||||
|
require = [nodeConfig];
|
||||||
|
services.etcd = {
|
||||||
|
initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
|
||||||
|
initialAdvertisePeerUrls = ["https://node2:2380"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
node3 = { config, pkgs, ... }: {
|
||||||
|
require = [nodeConfig];
|
||||||
|
services.etcd = {
|
||||||
|
initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380" "node3=https://node3:2380"];
|
||||||
|
initialAdvertisePeerUrls = ["https://node3:2380"];
|
||||||
|
initialClusterState = "existing";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
subtest "single node", sub {
|
subtest "should start etcd cluster", sub {
|
||||||
$simple->start();
|
|
||||||
$simple->waitForUnit("etcd.service");
|
|
||||||
$simple->waitUntilSucceeds("etcdctl set /foo/bar 'Hello world'");
|
|
||||||
$simple->waitUntilSucceeds("etcdctl get /foo/bar | grep 'Hello world'");
|
|
||||||
};
|
|
||||||
|
|
||||||
subtest "multy node", sub {
|
|
||||||
$node1->start();
|
$node1->start();
|
||||||
$node2->start();
|
$node2->start();
|
||||||
$node1->waitForUnit("etcd.service");
|
$node1->waitForUnit("etcd.service");
|
||||||
$node2->waitForUnit("etcd.service");
|
$node2->waitForUnit("etcd.service");
|
||||||
$node1->waitUntilSucceeds("etcdctl set /foo/bar 'Hello world'");
|
$node2->waitUntilSucceeds("etcdctl cluster-health");
|
||||||
$node2->waitUntilSucceeds("etcdctl get /foo/bar | grep 'Hello world'");
|
$node1->succeed("etcdctl set /foo/bar 'Hello world'");
|
||||||
$node1->shutdown();
|
$node2->succeed("etcdctl get /foo/bar | grep 'Hello world'");
|
||||||
$node2->shutdown();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
subtest "discovery", sub {
|
subtest "should add another member", sub {
|
||||||
$simple->succeed("curl -X PUT http://localhost:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=2");
|
$node1->succeed("etcdctl member add node3 https://node3:2380");
|
||||||
|
$node3->start();
|
||||||
|
$node3->waitForUnit("etcd.service");
|
||||||
|
$node3->waitUntilSucceeds("etcdctl member list | grep 'node3'");
|
||||||
|
$node3->succeed("etcdctl cluster-health");
|
||||||
|
};
|
||||||
|
|
||||||
$discovery1->start();
|
subtest "should survive member crash", sub {
|
||||||
$discovery2->start();
|
$node3->crash;
|
||||||
$discovery1->waitForUnit("etcd.service");
|
$node1->succeed("etcdctl cluster-health");
|
||||||
$discovery2->waitForUnit("etcd.service");
|
$node1->succeed("etcdctl set /foo/bar 'Hello degraded world'");
|
||||||
$discovery1->waitUntilSucceeds("etcdctl set /foo/bar 'Hello world'");
|
$node1->succeed("etcdctl get /foo/bar | grep 'Hello degraded world'");
|
||||||
$discovery2->waitUntilSucceeds("etcdctl get /foo/bar | grep 'Hello world'");
|
|
||||||
};
|
};
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user