Commit 222a57fc authored by pfandzelter's avatar pfandzelter
Browse files

refactor CA certificate use

parent 444e70bc
......@@ -6,7 +6,6 @@ LABEL maintainer="tp@mcc.tu-berlin.de"
WORKDIR /go/src/git.tu-berlin.de/mcc-fred/fred/
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
COPY nase/tls/ca.crt /usr/local/share/ca-certificates/ca.crt
RUN update-ca-certificates
# Make an extra layer for the installed packages so that they dont have to be downloaded everytime
......
......@@ -52,7 +52,7 @@ Alternatively, you are of course able to run every component manually.
#### Certificates
Before deploying `etcd` and `fred` software, certificates have to be created to secure the connection between all components and authenticate the individual services.
The `./nase/tls` folder includes a number of existing certificates and tooling to create new certificates.
We provide tooling to create new certificates.
More information about authentication and authorization with certificates can be found further down in this introduction.
This guide is adapted from [scriptcrunch.com](https://scriptcrunch.com/create-ca-tls-ssl-certificates-keys/).
......@@ -61,8 +61,6 @@ This guide is adapted from [scriptcrunch.com](https://scriptcrunch.com/create-ca
First, a certificate authority (CA) has to be created to issue new certificates.
```bash
cd ./nase/tls
# generate a CA private key
openssl genrsa -out ca.key 2048
......@@ -100,11 +98,9 @@ Although not strictly required, it is recommended to generate unique certificate
In this example case, the following commands are required:
```bash
cd ./nase/tls
./gen-cert.sh etcdnase 172.26.1.1
./gen-cert.sh frednode 172.26.1.2
./gen-cert.sh fredclient 172.26.1.3
cd ../..
```
#### Network
......@@ -122,9 +118,9 @@ To start a simple `etcd` instance in Docker with our certificates mounted as vol
```bash
docker pull quay.io/coreos/etcd:v3.4.10
docker run -d \
-v $(pwd)/nase/tls/etcdnase.crt:/cert/etcdnase.crt \
-v $(pwd)/nase/tls/etcdnase.key:/cert/etcdnase.key \
-v $(pwd)/nase/tls/ca.crt:/cert/ca.crt \
-v $(pwd)/etcdnase.crt:/cert/etcdnase.crt \
-v $(pwd)/etcdnase.key:/cert/etcdnase.key \
-v $(pwd)/ca.crt:/cert/ca.crt \
--network=fredwork \
--ip=172.26.1.1 \
quay.io/coreos/etcd:v3.4.10 \
......@@ -152,9 +148,9 @@ Running the `fred` software is similar to running `etcd`, but the image has to b
```bash
docker build -t fred .
docker run -d \
-v $(pwd)/nase/tls/frednode.crt:/cert/frednode.crt \
-v $(pwd)/nase/tls/frednode.key:/cert/frednode.key \
-v $(pwd)/nase/tls/ca.crt:/cert/ca.crt \
-v $(pwd)/frednode.crt:/cert/frednode.crt \
-v $(pwd)/frednode.key:/cert/frednode.key \
-v $(pwd)/ca.crt:/cert/ca.crt \
--network=fredwork \
--ip=172.26.1.2 \
fred \
......@@ -171,6 +167,7 @@ fred --log-level info \
--nase-ca /cert/ca.crt \
--trigger-cert /cert/frednode.crt \
--trigger-key /cert/frednode.key \
--trigger-ca /cert/ca.crt
--cert /cert/frednode.crt \
--key /cert/frednode.key \
--ca-file /cert/ca.crt
......@@ -187,9 +184,9 @@ If you want to try it out, use the `client.proto` in `./proto` to build a client
```bash
docker build -t grpcc -f grpcc.Dockerfile .
docker run \
-v $(pwd)/nase/tls/fredclient.crt:/cert/fredclient.crt \
-v $(pwd)/nase/tls/fredclient.key:/cert/fredclient.key \
-v $(pwd)/nase/tls/ca.crt:/cert/ca.crt \
-v $(pwd)/fredclient.crt:/cert/fredclient.crt \
-v $(pwd)/fredclient.key:/cert/fredclient.key \
-v $(pwd)/ca.crt:/cert/ca.crt \
-v $(pwd)/proto/client/client.proto:/client.proto \
--network=fredwork \
--ip=172.26.1.3 \
......
......@@ -6,7 +6,6 @@ LABEL maintainer="tp@mcc.tu-berlin.de"
WORKDIR /go/src/git.tu-berlin.de/mcc-fred/fred/
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
COPY nase/tls/ca.crt /usr/local/share/ca-certificates/ca.crt
RUN update-ca-certificates
# Make an extra layer for the installed packages so that they dont have to be downloaded everytime
......
......@@ -6,6 +6,7 @@ import (
"os/signal"
"runtime"
"runtime/pprof"
"strings"
"syscall"
"github.com/caarlos0/env/v6"
......@@ -61,6 +62,7 @@ type fredConfig struct {
Host string `env:"REMOTE_STORAGE_HOST"`
Cert string `env:"REMOTE_STORAGE_CERT"`
Key string `env:"REMOTE_STORAGE_KEY"`
CA string `env:"REMOTE_STORAGE_CA"`
}
DynamoDB struct {
Table string `env:"DYNAMODB_TABLE"`
......@@ -74,6 +76,7 @@ type fredConfig struct {
Trigger struct {
Cert string `env:"TRIGGER_CERT"`
Key string `env:"TRIGGER_KEY"`
CA string `env:"TRIGGER_CA"`
}
Profiling struct {
CPUProfPath string `env:"PROFILING_CPU_PATH"`
......@@ -109,6 +112,7 @@ func parseArgs() (fc fredConfig) {
flag.StringVar(&(fc.RemoteStore.Host), "remote-storage-host", "", "Host address of GRPC Server for storage connection. (Env: REMOTE_STORAGE_HOST)")
flag.StringVar(&(fc.RemoteStore.Cert), "remote-storage-cert", "", "Certificate for storage connection. (Env: REMOTE_STORAGE_CERT)")
flag.StringVar(&(fc.RemoteStore.Key), "remote-storage-key", "", "Key file for storage connection. (Env: REMOTE_STORAGE_KEY)")
flag.StringVar(&(fc.RemoteStore.CA), "remote-storage-ca", "", "Comma-separated list of CA certificate files for storage connection. (Env: REMOTE_STORAGE_KEY)")
flag.StringVar(&(fc.DynamoDB.Table), "dynamo-table", "", "AWS table for DynamoDB storage backend. (Env: DYNAMODB_TABLE)")
flag.StringVar(&(fc.DynamoDB.Region), "dynamo-region", "", "AWS region for DynamoDB storage backend. (Env: DYNAMODB_REGION)")
......@@ -132,6 +136,7 @@ func parseArgs() (fc fredConfig) {
// trigger node tls configuration
flag.StringVar(&(fc.Trigger.Cert), "trigger-cert", "", "Certificate for trigger node connection. (Env: TRIGGER_CERT)")
flag.StringVar(&(fc.Trigger.Key), "trigger-key", "", "Key file for trigger node connection. (Env: TRIGGER_KEY)")
flag.StringVar(&(fc.Trigger.CA), "trigger-ca", "", "Comma-separated list of CA certificate files for trigger node connection. (Env: TRIGGER_CA)")
flag.StringVar(&(fc.Profiling.CPUProfPath), "cpuprofile", "", "Enable CPU profiling and specify path for pprof output")
flag.StringVar(&(fc.Profiling.MemProfPath), "memprofile", "", "Enable memory profiling and specify path for pprof output")
......@@ -260,7 +265,7 @@ func main() {
case "memory":
store = badgerdb.NewMemory()
case "remote":
store = storageclient.NewClient(fc.RemoteStore.Host, fc.RemoteStore.Cert, fc.RemoteStore.Key)
store = storageclient.NewClient(fc.RemoteStore.Host, fc.RemoteStore.Cert, fc.RemoteStore.Key, strings.Split(fc.RemoteStore.CA, ","))
case "dynamo":
store, err = dynamo.New(fc.DynamoDB.Table, fc.DynamoDB.Region)
if err != nil {
......@@ -302,6 +307,7 @@ func main() {
ExternalHostProxy: fc.Server.Proxy,
TriggerCert: fc.Trigger.Cert,
TriggerKey: fc.Trigger.Key,
TriggerCA: strings.Split(fc.Trigger.CA, ","),
})
log.Debug().Msg("Starting Interconnection Server...")
......
......@@ -78,6 +78,7 @@ func startServer(cert string, key string, ca string, host string, wsHost string)
}
rootCAs.AppendCertsFromPEM(loaded)
// Create the credentials and return it
config := &tls.Config{
Certificates: []tls.Certificate{serverCert},
......
......@@ -7,7 +7,6 @@ LABEL maintainer="tp@mcc.tu-berlin.de"
WORKDIR /go/src/git.tu-berlin.de/mcc-fred/fred/
RUN apt update && apt install -y ca-certificates git && rm -rf /var/cache/apk/*
COPY nase/tls/ca.crt /usr/local/share/ca-certificates/ca.crt
RUN update-ca-certificates
RUN go get github.com/go-delve/delve/cmd/dlv
......
# Generating etcd certificates
You need certificates to properly talk to etcd with authentication and encryption.
Use the `gen-cert.sh` script to generate client and server certificates with `gen-cert.sh server 172.26.1.1` to generate a `server.crt` and `server.key` for a server with IP address `172.26.1.1`.
You can use the same script to generate client certificates and peer certificates, it doesn't matter.
But please down delete the CA files.
pls.
\ No newline at end of file
......@@ -2,6 +2,8 @@ package alexandra
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
alexandraProto "git.tu-berlin.de/mcc-fred/fred/proto/middleware"
"github.com/rs/zerolog/log"
......@@ -10,8 +12,8 @@ import (
)
type ClientsMgr struct {
clients map[string]*Client
clientsCert, clientsKey string
clients map[string]*Client
clientsCert, clientsKey, clientsCA string
}
type Client struct {
......@@ -19,11 +21,12 @@ type Client struct {
conn *grpc.ClientConn
}
func newClientsManager(clientsCert, clientsKey string) *ClientsMgr {
func newClientsManager(clientsCert, clientsKey, clientsCA string) *ClientsMgr {
return &ClientsMgr{
clients: make(map[string]*Client),
clientsCert: clientsCert,
clientsKey: clientsKey,
clientsCA: clientsCA,
}
}
......@@ -32,12 +35,12 @@ func (m *ClientsMgr) GetClientTo(host string) (client *Client) {
if client != nil {
return
}
client = newClient(host, m.clientsCert, m.clientsKey)
client = newClient(host, m.clientsCert, m.clientsKey, m.clientsCA)
m.clients[host] = client
return
}
func newClient(host, certFile, keyFile string) *Client {
func newClient(host, certFile, keyFile, caFile string) *Client {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
......@@ -45,8 +48,20 @@ func newClient(host, certFile, keyFile string) *Client {
return nil
}
// Create a new cert pool and add our own CA certificate
rootCAs := x509.NewCertPool()
loaded, err := ioutil.ReadFile(caFile)
if err != nil {
log.Fatal().Msgf("unexpected missing certfile: %v", err)
}
rootCAs.AppendCertsFromPEM(loaded)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: rootCAs,
}
tc := credentials.NewTLS(tlsConfig)
......
......@@ -67,7 +67,7 @@ func NewServer(host string, caCert string, serverCert string, serverKey string,
rootCAs,
isProxied,
proxyHost,
newClientsManager(nodesCert, nodesKey),
newClientsManager(nodesCert, nodesKey, caCert),
lighthouse,
lis,
grpc.NewServer(
......
......@@ -17,6 +17,7 @@ type Config struct {
NodeID string
TriggerCert string
TriggerKey string
TriggerCA []string
}
// Fred is an instance of FReD.
......@@ -82,7 +83,7 @@ func New(config *Config) (f Fred) {
r := newReplicationService(s, config.Client, config.NaSe)
t := newTriggerService(s, config.TriggerCert, config.TriggerKey)
t := newTriggerService(s, config.TriggerCert, config.TriggerKey, config.TriggerCA)
a := newAuthService(config.NaSe)
......
......@@ -18,7 +18,7 @@ import (
)
const (
certBasePath = "../../nase/tls/"
certBasePath = "../../tests/runner/certificates/"
etcdDir = ".default.etcd"
)
......@@ -73,7 +73,7 @@ func TestMain(m *testing.M) {
<-e.Server.ReadyNotify()
n, err := etcdnase.NewNameService(nodeID, []string{"127.0.0.1:6000"}, certBasePath+"client.crt", certBasePath+"client.key", certBasePath+"ca.crt")
n, err := etcdnase.NewNameService(nodeID, []string{"127.0.0.1:6000"}, certBasePath+"nodeA.crt", certBasePath+"nodeA.key", certBasePath+"ca.crt")
if err != nil {
panic(err)
......@@ -88,8 +88,9 @@ func TestMain(m *testing.M) {
ExternalHost: "127.0.0.1:9000",
ExternalHostProxy: "",
NodeID: nodeID,
TriggerCert: certBasePath + "client.crt",
TriggerKey: certBasePath + "client.key",
TriggerCert: certBasePath + "nodeA.crt",
TriggerKey: certBasePath + "nodeA.key",
TriggerCA: []string{certBasePath + "ca.crt"},
}
f = fred.New(&config)
......
......@@ -3,6 +3,8 @@ package fred
import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"git.tu-berlin.de/mcc-fred/fred/proto/trigger"
"github.com/go-errors/errors"
......@@ -53,7 +55,7 @@ func dealWithStatusResponse(res *trigger.TriggerResponse, err error, from string
}
func newTriggerService(s *storeService, certFile, keyFile string) *triggerService {
func newTriggerService(s *storeService, certFile string, keyFile string, caFiles []string) *triggerService {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
......@@ -61,9 +63,28 @@ func newTriggerService(s *storeService, certFile, keyFile string) *triggerServic
return nil
}
// Create a new cert pool and add our own CA certificate
rootCAs, err := x509.SystemCertPool()
if err != nil {
log.Fatal().Err(err).Msg("Cannot load root certificates")
return nil
}
for _, f := range caFiles {
loaded, err := ioutil.ReadFile(f)
if err != nil {
log.Fatal().Msgf("unexpected missing certfile: %v", err)
}
rootCAs.AppendCertsFromPEM(loaded)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
RootCAs: rootCAs,
}
tc := credentials.NewTLS(tlsConfig)
......
......@@ -3,7 +3,9 @@ package storageclient
import (
"context"
"crypto/tls"
"crypto/x509"
"io"
"io/ioutil"
"git.tu-berlin.de/mcc-fred/fred/proto/storage"
"github.com/go-errors/errors"
......@@ -19,7 +21,7 @@ type Client struct {
}
// NewClient Client creates a new Client to communicate with a GRpc server
func NewClient(host, certFile, keyFile string) *Client {
func NewClient(host, certFile string, keyFile string, caFiles []string) *Client {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
......@@ -28,9 +30,28 @@ func NewClient(host, certFile, keyFile string) *Client {
return nil
}
// Create a new cert pool and add our own CA certificate
rootCAs, err := x509.SystemCertPool()
if err != nil {
log.Fatal().Err(err).Msg("Cannot load root certificates")
return nil
}
for _, f := range caFiles {
loaded, err := ioutil.ReadFile(f)
if err != nil {
log.Fatal().Msgf("unexpected missing certfile: %v", err)
}
rootCAs.AppendCertsFromPEM(loaded)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
RootCAs: rootCAs,
}
tc := credentials.NewTLS(tlsConfig)
......
......@@ -7,7 +7,6 @@ LABEL maintainer="tp@mcc.tu-berlin.de"
WORKDIR /go/src/git.tu-berlin.de/mcc-fred/fred/
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
COPY nase/tls/ca.crt /usr/local/share/ca-certificates/ca.crt
RUN update-ca-certificates
# Make an extra layer for the installed packages so that they dont have to be downloaded everytime
......
......@@ -7,7 +7,7 @@ LABEL maintainer="tp@mcc.tu-berlin.de"
WORKDIR /go/src/git.tu-berlin.de/mcc-fred/fred/
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
COPY nase/tls/ca.crt /usr/local/share/ca-certificates/ca.crt
#COPY nase/tls/ca.crt /usr/local/share/ca-certificates/ca.crt
RUN update-ca-certificates
# Make an extra layer for the installed packages so that they dont have to be downloaded everytime
......
......@@ -51,6 +51,7 @@ func main() {
certFile := flag.String("cert-file", "", "Certificate to talk to FReD")
keyFile := flag.String("key-file", "", "Keyfile to talk to FReD")
caFile := flag.String("ca-file", "", "Root certificate used to sign client certificates")
littleCertFile := flag.String("little-cert-file", "", "Certificate to talk to FReD as \"littleclient\"")
littleKeyFile := flag.String("little-key-file", "", "Keyfile to talk to FReD as \"littleclient\"")
......@@ -60,13 +61,13 @@ func main() {
flag.Parse()
port, _ := strconv.Atoi(*nodeAhttpPort)
nodeA := grpcclient.NewNode(*nodeAhost, port, *nodeApeeringID, *certFile, *keyFile)
nodeA := grpcclient.NewNode(*nodeAhost, port, *nodeApeeringID, *certFile, *keyFile, *caFile)
port, _ = strconv.Atoi(*nodeBhttpPort)
nodeB := grpcclient.NewNode(*nodeBhost, port, *nodeBpeeringID, *certFile, *keyFile)
nodeB := grpcclient.NewNode(*nodeBhost, port, *nodeBpeeringID, *certFile, *keyFile, *caFile)
port, _ = strconv.Atoi(*nodeChttpPort)
nodeC := grpcclient.NewNode(*nodeChost, port, *nodeCpeeringID, *certFile, *keyFile)
nodeC := grpcclient.NewNode(*nodeChost, port, *nodeCpeeringID, *certFile, *keyFile, *caFile)
port, _ = strconv.Atoi(*nodeAhttpPort)
littleClient := grpcclient.NewNode(*nodeAhost, port, *nodeApeeringID, *littleCertFile, *littleKeyFile)
littleClient := grpcclient.NewNode(*nodeAhost, port, *nodeApeeringID, *littleCertFile, *littleKeyFile, *caFile)
time.Sleep(15 * time.Second)
......
......@@ -3,7 +3,9 @@ package grpcclient
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"git.tu-berlin.de/mcc-fred/fred/proto/client"
"github.com/rs/zerolog/log"
......@@ -21,7 +23,7 @@ type Node struct {
}
// NewNode creates a new Node that represents a connection to a single fred instance
func NewNode(addr string, port int, id string, certFile, keyFile string) *Node {
func NewNode(addr string, port int, id string, certFile string, keyFile string, caFile string) *Node {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
......@@ -30,9 +32,28 @@ func NewNode(addr string, port int, id string, certFile, keyFile string) *Node {
return nil
}
// from https://forfuncsake.github.io/post/2017/08/trust-extra-ca-cert-in-go-app/
// Get the SystemCertPool, continue with an empty pool on error
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}
// Read in the cert file
certs, err := ioutil.ReadFile(caFile)
if err != nil {
log.Fatal().Err(err).Msgf("Failed to append %q to RootCAs: %v", caFile, err)
}
// Append our cert to the system pool
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
log.Fatal().Err(err).Msgf("No certs appended, using system certs only")
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
RootCAs: rootCAs,
}
tc := credentials.NewTLS(tlsConfig)
......
......@@ -7,7 +7,6 @@ LABEL maintainer="tp@mcc.tu-berlin.de"
WORKDIR /go/src/git.tu-berlin.de/mcc-fred/fred/
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
COPY nase/tls/ca.crt /usr/local/share/ca-certificates/ca.crt
RUN update-ca-certificates
# Make an extra layer for the installed packages so that they dont have to be downloaded everytime
......
......@@ -3,6 +3,8 @@ package client
import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
alexandra "git.tu-berlin.de/mcc-fred/fred/proto/middleware"
"github.com/rs/zerolog/log"
......@@ -22,6 +24,17 @@ func NewAlexandraClient(address string) AlexandraClient {
return AlexandraClient{}
}
// Create a new cert pool and add our own CA certificate
rootCAs := x509.NewCertPool()
loaded, err := ioutil.ReadFile("/cert/ca.crt")
if err != nil {
log.Fatal().Msgf("unexpected missing certfile: %v", err)
}
rootCAs.AppendCertsFromPEM(loaded)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment