feat/tls: enable draft/draftd TLS

This commit is contained in:
fibonacci1729 2017-12-04 16:16:40 -07:00
Родитель 095204f1e1
Коммит f68a574f81
12 изменённых файлов: 629 добавлений и 36 удалений

Просмотреть файл

@ -16,6 +16,7 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/tlsutil"
"github.com/Azure/draft/pkg/draft"
"github.com/Azure/draft/pkg/draft/draftpath"
@ -41,6 +42,16 @@ var (
draftHost string
// draftNamespace depicts which namespace the Draftd server is running in. This is used when Draftd was installed in a different namespace than kube-system.
draftNamespace string
// tls flags, options, and defaults
tlsCaCertFile string // path to TLS CA certificate file
tlsCertFile string // path to TLS certificate file
tlsKeyFile string // path to TLS key file
tlsVerify bool // enable TLS and verify remote certificates
tlsEnable bool // enable TLS
tlsCaCertDefault = "$DRAFT_HOME/ca.pem"
tlsCertDefault = "$DRAFT_HOME/cert.pem"
tlsKeyDefault = "$DRAFT_HOME/key.pem"
)
var globalUsage = `The application deployment tool for Kubernetes.
@ -56,6 +67,7 @@ func newRootCmd(out io.Writer, in io.Reader) *cobra.Command {
if flagDebug {
log.SetLevel(log.DebugLevel)
}
os.Setenv(homeEnvVar, draftHome)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
teardown()
@ -72,8 +84,8 @@ func newRootCmd(out io.Writer, in io.Reader) *cobra.Command {
newCreateCmd(out),
newHomeCmd(out),
newInitCmd(out, in),
newUpCmd(out),
newVersionCmd(out),
addFlagsTLS(newUpCmd(out)),
addFlagsTLS(newVersionCmd(out)),
newPluginCmd(out),
newConnectCmd(out),
newDeleteCmd(out),
@ -116,14 +128,43 @@ func teardown() {
}
func ensureDraftClient(client *draft.Client) *draft.Client {
if client == nil {
return draft.NewClient(&draft.ClientConfig{
ServerAddr: draftHost,
Stdout: os.Stdout,
Stderr: os.Stderr,
})
if client != nil {
return client
}
return client
return newClient()
}
func newClient() *draft.Client {
cfg := &draft.ClientConfig{ServerAddr: draftHost, Stdout: os.Stdout, Stderr: os.Stderr}
if tlsVerify || tlsEnable {
if tlsCaCertFile == tlsCaCertDefault {
tlsCaCertFile = os.ExpandEnv(tlsCaCertDefault)
}
if tlsCertFile == tlsCertDefault {
tlsCertFile = os.ExpandEnv(tlsCertDefault)
}
if tlsKeyFile == tlsKeyDefault {
tlsKeyFile = os.ExpandEnv(tlsKeyDefault)
}
debug("Key=%q, Cert=%q, CA=%q\n", tlsKeyFile, tlsCertFile, tlsCaCertFile)
tlsopts := tlsutil.Options{
InsecureSkipVerify: true,
CertFile: tlsCertFile,
KeyFile: tlsKeyFile,
}
if tlsVerify {
tlsopts.InsecureSkipVerify = false
tlsopts.CaCertFile = tlsCaCertFile
}
tlscfg, err := tlsutil.ClientConfig(tlsopts)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
cfg.UseTLS = true
cfg.TLSConfig = tlscfg
}
return draft.NewClient(cfg)
}
func defaultDraftHost() string {
@ -189,3 +230,15 @@ func validateArgs(args, expectedArgs []string) error {
}
return nil
}
// addFlagsTLS adds the flags for supporting client side TLS to the
// helm command (only those that invoke communicate to Draftd.)
func addFlagsTLS(cmd *cobra.Command) *cobra.Command {
// add flags
cmd.Flags().StringVar(&tlsCaCertFile, "tls-ca-cert", tlsCaCertDefault, "path to TLS CA certificate file")
cmd.Flags().StringVar(&tlsCertFile, "tls-cert", tlsCertDefault, "path to TLS certificate file")
cmd.Flags().StringVar(&tlsKeyFile, "tls-key", tlsKeyDefault, "path to TLS key file")
cmd.Flags().BoolVar(&tlsVerify, "tls-verify", false, "enable TLS for request and verify remote")
cmd.Flags().BoolVar(&tlsEnable, "tls", false, "enable TLS for request")
return cmd
}

Просмотреть файл

@ -7,7 +7,6 @@ import (
"fmt"
"io"
"os"
"strconv"
"strings"
"syscall"
@ -37,15 +36,6 @@ $DRAFT_HOME, but not attempt to connect to a remote cluster and install the Draf
deployment.
To dump information about the Draft chart, combine the '--dry-run' and '--debug' flags.
`
chartConfigTpl = `
ingress:
enabled: %s
basedomain: %s
registry:
url: %s
authtoken: %s
imageOverride: %s
`
)
@ -86,10 +76,41 @@ func newInitCmd(out io.Writer, in io.Reader) *cobra.Command {
f.BoolVar(&i.autoAccept, "auto-accept", false, "automatically accept configuration defaults (if detected). It will still prompt for information if this is set to true and no cloud provider was found")
f.BoolVar(&i.dryRun, "dry-run", false, "go through all the steps without actually installing anything. Mostly used along with --debug for debugging purposes.")
f.StringVarP(&i.image, "draftd-image", "i", "", "override Draftd image")
// flags for bootstrapping draftd with tls
f.BoolVar(&tlsEnable, "draftd-tls", false, "install Draftd with TLS enabled")
f.BoolVar(&tlsVerify, "draftd-tls-verify", false, "install Draftd with TLS enabled and to verify remote certificates")
f.StringVar(&tlsKeyFile, "draftd-tls-key", "", "path to TLS key file to install with Draftd")
f.StringVar(&tlsCertFile, "draftd-tls-cert", "", "path to TLS certificate file to install with Draftd")
f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate")
return cmd
}
// tlsOptions sanitizes the tls flags as well as checks for the existence of
// required tls files indicate by those flags, if any.
func (i *initCmd) tlsOptions(cfg *installerConfig.DraftConfig) error {
cfg.EnableTLS = tlsEnable || tlsVerify
cfg.VerifyTLS = tlsVerify
if cfg.EnableTLS {
missing := func(file string) bool {
_, err := os.Stat(file)
return os.IsNotExist(err)
}
if cfg.TLSKeyFile = tlsKeyFile; cfg.TLSKeyFile == "" || missing(cfg.TLSKeyFile) {
return errors.New("missing required TLS key file")
}
if cfg.TLSCertFile = tlsCertFile; cfg.TLSCertFile == "" || missing(cfg.TLSCertFile) {
return errors.New("missing required TLS certificate file")
}
if cfg.VerifyTLS {
if cfg.TLSCaCertFile = tlsCaCertFile; cfg.TLSCaCertFile == "" || missing(cfg.TLSCaCertFile) {
return errors.New("missing required TLS CA file")
}
}
}
return nil
}
// runInit initializes local config and installs Draft to Kubernetes Cluster
func (i *initCmd) run() error {
@ -155,15 +176,18 @@ func (i *initCmd) run() error {
}
rawChartConfig := fmt.Sprintf(chartConfigTpl, strconv.FormatBool(draftConfig.Ingress), draftConfig.Basedomain, draftConfig.RegistryURL, draftConfig.RegistryAuth, draftConfig.Image)
log.Debugf("raw chart config: %s", rawChartConfig)
if err := i.tlsOptions(draftConfig); err != nil {
return err
}
log.Debugf("raw chart config: %s", draftConfig)
if !i.dryRun {
// attempt to purge the old release, but log errors to --debug
if err := installer.Uninstall(i.helmClient); err != nil {
log.Debugf("error uninstalling Draft: %s", err)
}
if err := installer.Install(i.helmClient, draftNamespace, rawChartConfig); err != nil {
if err := installer.Install(i.helmClient, draftNamespace, draftConfig); err != nil {
return fmt.Errorf("error installing Draft: %s", err)
}
}

Просмотреть файл

@ -1,15 +1,67 @@
package config
import (
b64 "encoding/base64"
"fmt"
"io/ioutil"
"k8s.io/client-go/tools/clientcmd"
"os"
"strconv"
)
const chartConfigTpl = `
ingress:
enabled: %s
basedomain: %s
registry:
url: %s
authtoken: %s
imageOverride: %s
`
const chartConfigTLSTpl = `
tls:
enable: %s
verify: %s
key: %s
cert: %s
cacert: %s
`
type DraftConfig struct {
Basedomain string
Image string
Ingress bool
RegistryURL string
RegistryAuth string
//
// TLS configurations for Draftd
//
// EnableTLS instructs Draftd to serve with TLS enabled.
//
// Implied by VerifyTLS. If set the TLSKey and TLSCert are required.
EnableTLS bool
// VerifyTLS instructs Draftd to serve with TLS enabled verify remote certificates.
//
// If set TLSKey, TLSCert, TLSCaCert are required.
VerifyTLS bool
// TLSKeyFile identifies the file containing the pem encoded TLS private
// key Draftd should use.
//
// Required and valid if and only if EnableTLS or VerifyTLS is set.
TLSKeyFile string
// TLSCertFile identifies the file containing the pem encoded TLS
// certificate Draftd should use.
//
// Required and valid if and only if EnableTLS or VerifyTLS is set.
TLSCertFile string
// TLSCaCertFile identifies the file containing the pem encoded TLS CA
// certificate Draftd should use to verify remotes certificates.
//
// Required and valid if and only if VerifyTLS is set.
TLSCaCertFile string
}
// FromClientConfig reads a kubernetes client config, searching for information that may indicate
@ -36,3 +88,40 @@ func FromClientConfig(config clientcmd.ClientConfig) (*DraftConfig, string, erro
return draftConfig, cloudProviderName, nil
}
func (cfg *DraftConfig) WithTLS() bool { return cfg.EnableTLS || cfg.VerifyTLS }
// String returns the string representation of a DraftConfig.
func (cfg *DraftConfig) String() string {
cfgstr := fmt.Sprintf(
chartConfigTpl,
strconv.FormatBool(cfg.Ingress),
cfg.Basedomain,
cfg.RegistryURL,
cfg.RegistryAuth,
cfg.Image,
)
if cfg.WithTLS() {
cfgstr += addChartConfigTLSTpl(cfg)
}
return cfgstr
}
func addChartConfigTLSTpl(cfg *DraftConfig) string {
return fmt.Sprintf(chartConfigTLSTpl,
strconv.FormatBool(cfg.EnableTLS),
strconv.FormatBool(cfg.VerifyTLS),
b64.StdEncoding.EncodeToString(readfile(cfg.TLSKeyFile)),
b64.StdEncoding.EncodeToString(readfile(cfg.TLSCertFile)),
b64.StdEncoding.EncodeToString(readfile(cfg.TLSCaCertFile)),
)
}
func readfile(path string) []byte {
b, err := ioutil.ReadFile(path)
if err != nil {
fmt.Fprintf(os.Stderr, "fatal: reading file %q: %v\n", path, err)
os.Exit(2)
}
return b
}

Просмотреть файл

@ -10,6 +10,7 @@ import (
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
draftconfig "github.com/Azure/draft/cmd/draft/installer/config"
"github.com/Azure/draft/pkg/version"
)
@ -54,6 +55,12 @@ registry:
#
# $ echo '{"registrytoken":"9cbaf023786cd7"}' | base64 -w 0
authtoken: e30K
tls:
enable: false
verify: false
key: ""
cert: ""
cacert: ""
`
const draftIgnore = `# Patterns to ignore when building packages.
@ -104,6 +111,8 @@ spec:
- --registry-auth={{ .Values.registry.authtoken }}
- --basedomain={{ .Values.ingress.basedomain }}
- --ingress-enabled={{.Values.ingress.enabled}}
- --tls={{.Values.tls.enable}}
- --tls-verify={{.Values.tls.verify}}
{{- if .Values.debug }}
- --debug
{{- end }}
@ -114,6 +123,12 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: DRAFT_TLS_VERIFY
value: {{ if .Values.tls.verify }}"1"{{ else }}"0"{{ end }}
- name: DRAFT_TLS_ENABLE
value: {{ if .Values.tls.enable }}"1"{{ else }}"0"{{ end }}
- name: DRAFT_TLS_CERTS
value: "/etc/certs"
livenessProbe:
httpGet:
path: /ping
@ -125,10 +140,20 @@ spec:
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-socket
{{- if (or .Values.tls.enable .Values.tls.verify) }}
- mountPath: "/etc/certs"
name: draftd-certs
readOnly: true
{{- end }}
volumes:
- name: docker-socket
hostPath:
path: /var/run/docker.sock
{{- if (or .Values.tls.enable .Values.tls.verify) }}
- name: draftd-certs
secret:
secretName: draftd-secret
{{- end }}
nodeSelector:
beta.kubernetes.io/os: linux
`
@ -146,6 +171,17 @@ spec:
app: {{ .Chart.Name }}
`
const draftSecret = `apiVersion: v1
kind: Secret
type: Opaque
data:
tls.key: {{ .Values.tls.key }}
tls.crt: {{ .Values.tls.cert }}
ca.crt: {{ .Values.tls.cacert }}
metadata:
name: draftd-secret
`
const draftNotes = `Now you can deploy an app using Draft!
$ cd my-app
@ -197,7 +233,15 @@ var DefaultChartFiles = []*chartutil.BufferedFile{
// Install uses the helm client to install Draftd with the given config.
//
// Returns an error if the command failed.
func Install(client *helm.Client, namespace string, rawChartConfig string) error {
func Install(client *helm.Client, namespace string, config *draftconfig.DraftConfig) error {
if config.WithTLS() {
DefaultChartFiles = append(DefaultChartFiles, &chartutil.BufferedFile{
// TODO: Add chartutil.SecretName to k8s.io/helm/pkg/chartutil/create.go
Name: path.Join(chartutil.TemplatesDir, "secret.yaml"),
Data: []byte(draftSecret),
})
}
chart, err := chartutil.LoadFiles(DefaultChartFiles)
if err != nil {
return err
@ -206,7 +250,8 @@ func Install(client *helm.Client, namespace string, rawChartConfig string) error
chart,
namespace,
helm.ReleaseName(ReleaseName),
helm.ValueOverrides([]byte(rawChartConfig)))
helm.ValueOverrides([]byte(config.String())),
)
return prettyError(err)
}

Просмотреть файл

@ -5,20 +5,68 @@
package main
import (
"crypto/tls"
"io"
"os"
"path/filepath"
log "github.com/Sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/tlsutil"
)
const (
// tlsEnableEnvVar names the environment variable that enables TLS.
tlsEnableEnvVar = "TILLER_TLS_ENABLE"
// tlsVerifyEnvVar names the environment variable that enables
// TLS, as well as certificate verification of the remote peer.
tlsVerifyEnvVar = "TILLER_TLS_VERIFY"
// tlsCertsEnvVar names the environment variable that points to
// the directory where Draftd's TLS certificates are located.
tlsCertsEnvVar = "TILLER_TLS_CERTS"
)
var (
// flagDebug is a signal that the user wants additional output.
flagDebug bool
// tls flags and options
tlsEnable bool
tlsVerify bool
keyFile string
certFile string
caCertFile string
)
var globalUsage = "The draft server."
func tlsEnableEnvVarDefault() bool { return os.Getenv(tlsEnableEnvVar) != "" }
func tlsVerifyEnvVarDefault() bool { return os.Getenv(tlsVerifyEnvVar) != "" }
func tlsDefaultsFromEnv(name string) (value string) {
switch certsDir := os.Getenv(tlsCertsEnvVar); name {
case "tls-key":
return filepath.Join(certsDir, "tls.key")
case "tls-cert":
return filepath.Join(certsDir, "tls.crt")
case "tls-ca-cert":
return filepath.Join(certsDir, "ca.crt")
}
return ""
}
func tlsOptions() tlsutil.Options {
opts := tlsutil.Options{CertFile: certFile, KeyFile: keyFile}
if tlsVerify {
opts.CaCertFile = caCertFile
// We want to force the client to not only provide a cert, but to
// provide a cert that we can validate.
//
// See: http://www.bite-code.com/2015/06/25/tls-mutual-auth-in-golang/
opts.ClientAuth = tls.RequireAndVerifyClientCert
}
return opts
}
func newRootCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "draftd",

Просмотреть файл

@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra"
"golang.org/x/net/context"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/tlsutil"
"github.com/Azure/draft/pkg/draft"
"github.com/Azure/draft/pkg/kube"
@ -71,6 +72,12 @@ func newStartCmd(out io.Writer) *cobra.Command {
f.StringVar(&sc.tillerURI, "tiller-uri", "tiller-deploy:44134", "the URI used to connect to tiller")
f.BoolVarP(&sc.local, "local", "", false, "run draftd locally (uses local kubecfg)")
f.BoolVarP(&sc.ingressEnabled, "ingress-enabled", "", false, "configure ingress")
// add TLS flags
f.BoolVar(&tlsEnable, "tls", tlsEnableEnvVarDefault(), "enable TLS")
f.BoolVar(&tlsVerify, "tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate")
f.StringVar(&keyFile, "tls-key", tlsDefaultsFromEnv("tls-key"), "path to TLS private key file")
f.StringVar(&certFile, "tls-cert", tlsDefaultsFromEnv("tls-cert"), "path to TLS certificate file")
f.StringVar(&caCertFile, "tls-ca-cert", tlsDefaultsFromEnv("tls-ca-cert"), "trust certificates signed by this CA")
return cmd
}
@ -104,9 +111,18 @@ func (c *startCmd) run() (err error) {
return fmt.Errorf("failed to create in-cluster kubernetes client: %v", err)
}
}
if tlsEnable || tlsVerify {
tlscfg, err := tlsutil.ServerConfig(tlsOptions())
if err != nil {
return fmt.Errorf("failed to create server TLS configuration: %v", err)
}
cfg.UseTLS = true
cfg.TLSConfig = tlscfg
}
cfg.Helm = helm.NewClient(helm.Host(c.tillerURI))
log.Printf("server is now listening at %s", c.listenAddr)
log.Printf("server is now listening at %s (tls=%t)", c.listenAddr, tlsEnable || tlsVerify)
return draft.NewServer(cfg).Serve(context.Background())
}

249
docs/draft_ssl.md Normal file
Просмотреть файл

@ -0,0 +1,249 @@
# Using SSL Between Draft and Draftd
This document explains how to create strong SSL/TLS connections between the
Draft client (Draft) and server (Draftd). The emphasis here is on creating an
internal CA, and using both the cryptographic and identity functions of SSL.
Configuring SSL is considered an advanced topic, and knowledge of Draft is assumed.
## Overview
The Draftd authentication model uses client-side SSL certificates. Draftd itself
verifies these certificates using a certificate authority. Likewise, the client
also verifies Draftd's identity by certificate authority.
NOTE: For now this document concerns configuring the Draft client and server to
communicate using SSL certificates. Specifically, TLS is used to secure gRPC
communication between the Draft client and server.
There are numerous possible configurations for setting up certificates and authorities,
but the method we cover here will work for most situations.
In this guide, we will show how to:
- Create a private CA that is used to issue certificates for Draftd clients and
servers.
- Create a certificate for Draftd
- Create a certificate for the Draft client
- Create a Draftd instance that uses the certificate
- Configure the Draft client to use the CA and client-side certificate
By the end of this guide, you should have a Draftd instance running that will
only accept connections from clients who can be authenticated by SSL certificate.
## Generating Certificate Authorities and Certificates
One way to generate SSL CAs is via the `openssl` command line tool. There are many
guides and best practices documents available online. This explanation is focused
on getting ready within a small amount of time. For production configurations,
we urge readers to read [the official documentation](https://www.openssl.org) and
consult other resources.
### Generate a Certificate Authority
The simplest way to generate a certificate authority is to run two commands:
```console
$ openssl genrsa -out ./ca.key.pem 4096
$ openssl req -key ca.key.pem -new -x509 -days 7300 -sha256 -out ca.cert.pem -extensions v3_ca
Enter pass phrase for ca.key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:CO
Locality Name (eg, city) []:Boulder
Organization Name (eg, company) [Internet Widgits Pty Ltd]:draft
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:draft
Email Address []:draftd@example.com
```
Note that the data input above is _sample data_. You should customize to your own
specifications.
The above will generate both a secret key and a CA. Note that these two files are
very important. The key in particular should be handled with particular care.
Often, you will want to generate an intermediate signing key. For the sake of brevity,
we will be signing keys with our root CA.
### Generating Certificates
We will be generating two certificates, each representing a type of certificate:
- One certificate is for Draftd. You will want one of these _per draftd host_ that
you run.
- One certificate is for the user. You will want one of these _per draft client_.
Since the commands to generate these are the same, we'll be creating both at the
same time. The names will indicate their target.
First, the Draftd key:
```console
$ openssl genrsa -out ./draftd.key.pem 4096
Generating RSA private key, 4096 bit long modulus
..........................................................................................................................................................................................................................................................................................................................++
............................................................................++
e is 65537 (0x10001)
Enter pass phrase for ./draftd.key.pem:
Verifying - Enter pass phrase for ./draftd.key.pem:
```
Next, generate the Draft client's key:
```console
$ openssl genrsa -out ./draft.key.pem 4096
Generating RSA private key, 4096 bit long modulus
.....++
......................................................................................................................................................................................++
e is 65537 (0x10001)
Enter pass phrase for ./draft.key.pem:
Verifying - Enter pass phrase for ./draft.key.pem:
```
Again, for production use you will generate one client certificate for each user.
Next we need to create certificates from these keys. For each certificate, this is
a two-step process of creating a CSR, and then creating the certificate.
```console
$ openssl req -key draftd.key.pem -new -sha256 -out draftd.csr.pem
Enter pass phrase for draftd.key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:CO
Locality Name (eg, city) []:Boulder
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Draftd Server
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:draftd-server
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
```
And we repeat this step for the Draft client certificate:
```console
$ openssl req -key draft.key.pem -new -sha256 -out draft.csr.pem
# Answer the questions with your client user's info
```
(In rare cases, we've had to add the `-nodes` flag when generating the request.)
Now we sign each of these CSRs with the CA certificate we created:
```console
$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in draftd.csr.pem -out draftd.cert.pem
Signature ok
subject=/C=US/ST=CO/L=Boulder/O=Draftd Server/CN=draftd-server
Getting CA Private Key
Enter pass phrase for ca.key.pem:
```
And again for the client certificate:
```console
$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in draft.csr.pem -out draft.cert.pem
```
At this point, the important files for us are these:
```
# The CA. Make sure the key is kept secret.
ca.cert.pem
ca.key.pem
# The Draft client files
draft.cert.pem
draft.key.pem
# The Draftd server files.
draftd.cert.pem
draftd.key.pem
```
Now we're ready to move on to the next steps.
## Creating a Custom Draftd Installation
The Draft client includes full support for creating a deployment configured for SSL.
By specifying a few flags, the `draft init` command can create a new Draftd installation
complete with all of our SSL configuration.
To take a look at what this will generate, run this command:
```console
$ draft init --dry-run --debug --draftd-tls --draftd-tls-cert ./draftd.cert.pem --draftd-tls-key ./draftd.key.pem --draftd-tls-verify --tls-ca-cert ca.cert.pem
```
The output will show you the values file used to install the Draft chart. Your SSL
information will be preloaded into the Secret, which the Deployment will mount to
pods as they start up.
## Configuring the Draft Client
The Draftd server is now running with TLS protection. It's time to configure the
Draft client to enable TLS-secured operations.
For a quick test, we can specify our configuration manually.
```console
draft up --tls --tls-ca-cert ca.cert.pem --tls-cert draft.cert.pem --tls-key draft.key.pem
```
This configuration sends our client-side certificate to establish identity, uses
the client key for encryption, and uses the CA certificate to validate the remote
Draftd's identity.
Typing a line that that is cumbersome, though. The shortcut is to move the key,
cert, and CA into `$DRAFT_HOME`:
```console
$ cp ca.cert.pem $(draft home)/ca.pem
$ cp draft.cert.pem $(draft home)/cert.pem
$ cp draft.key.pem $(draft home)/key.pem
```
With this, you can simply run `draft up --tls` to enable TLS.
### Troubleshooting
*Running a command, I get `Error: transport is closing`*
This is almost always due to a configuration error in which the client is missing
a certificate (`--tls-cert`) or the certificate is bad.
*I'm using a certificate, but get `Error: remote error: tls: bad certificate`*
This means that Draftd's CA cannot verify your certificate. In the examples above,
we used a single CA to generate both the client and server certificates. In these
examples, the CA has _signed_ the client's certificate. We then load that CA
up to Draftd. So when the client certificate is sent to the server, Draftd
checks the client certificate against the CA.
*If I use `--tls-verify` on the client, I get `Error: x509: certificate is valid for draftd-server, not localhost`*
If you plan to use `--tls-verify` on the client, you will need to make sure that
the host name that Draft connects to matches the host name on the certificate. In
some cases this is awkward, since Draft will connect over localhost, or the FQDN is
not available for public resolution.
## References
https://github.com/denji/golang-tls
https://www.openssl.org/docs/
https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html

Просмотреть файл

@ -1,10 +1,14 @@
package draft
import (
"crypto/tls"
"fmt"
"io"
"sync"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/Azure/draft/pkg/build"
"github.com/Azure/draft/pkg/rpc"
"github.com/Azure/draft/pkg/version"
@ -18,6 +22,8 @@ type ClientConfig struct {
ServerHost string
Stdout io.Writer
Stderr io.Writer
UseTLS bool
TLSConfig *tls.Config
}
type Client struct {
@ -29,6 +35,13 @@ type Client struct {
// NewClient takes ClientConfig and returns a Client
func NewClient(cfg *ClientConfig) *Client {
opts := []rpc.ClientOpt{rpc.WithServerAddr(cfg.ServerAddr)}
switch {
case cfg.UseTLS:
creds := grpc.WithTransportCredentials(credentials.NewTLS(cfg.TLSConfig))
opts = append(opts, rpc.WithGrpcDialOpt(creds))
default:
opts = append(opts, rpc.WithGrpcDialOpt(grpc.WithInsecure()))
}
return &Client{
cfg: cfg,
rpc: rpc.NewClient(opts...),

Просмотреть файл

@ -1,6 +1,7 @@
package draft
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
@ -17,6 +18,7 @@ import (
"github.com/docker/docker/pkg/term"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8s "k8s.io/client-go/kubernetes"
@ -36,6 +38,8 @@ type ServerConfig struct {
Docker *docker.Client
Helm helm.Interface
Kube k8s.Interface
UseTLS bool
TLSConfig *tls.Config
}
// Server is a draft Server.
@ -67,8 +71,15 @@ func (s *Server) Serve(ctx context.Context) error {
return err
}
var opts []rpc.ServerOpt
if s.cfg.UseTLS {
opts = append(opts, rpc.WithGrpcServerOpt(
grpc.Creds(credentials.NewTLS(s.cfg.TLSConfig)),
))
}
errc := make(chan error, 1)
s.srv = rpc.NewServer()
s.srv = rpc.NewServer(opts...)
wg.Add(1)
go func() {
errc <- s.srv.Serve(lis, s)

Просмотреть файл

@ -15,6 +15,7 @@ type clientImpl struct {
func newClientImpl(opts ...ClientOpt) Client {
var c clientImpl
opts = append(DefaultClientOpts(), opts...)
for _, opt := range opts {
opt(&c.opts)
}
@ -164,8 +165,8 @@ func up_stream(ctx context.Context, rpc DraftClient, send <-chan *UpRequest, rec
}
// connect connects the DraftClient to the DraftServer.
func connect(c *clientImpl, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
if conn, err = grpc.Dial(c.opts.addr, grpc.WithInsecure()); err != nil {
func connect(c *clientImpl) (conn *grpc.ClientConn, err error) {
if conn, err = grpc.Dial(c.opts.addr, c.opts.dialOpts...); err != nil {
return nil, fmt.Errorf("failed to dial %q: %v", c.opts.addr, err)
}
return conn, nil

Просмотреть файл

@ -1,11 +1,18 @@
package rpc
import (
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
"time"
)
// clientOpts specifies the union of all configurable
// options an rpc.Client accepts to communicate with
// the draft rpc.Server.
type clientOpts struct {
addr string
host string
dialOpts []grpc.DialOption
addr string
host string
}
// WithServerAddr sets the draft server address
@ -24,6 +31,46 @@ func WithServerHost(host string) ClientOpt {
}
}
// WithGrpcDialOpt adds the provided grpc.DialOptions for the
// rpc.Client to use when initializing the underlying grpc.Client.
func WithGrpcDialOpt(dialOpts ...grpc.DialOption) ClientOpt {
return func(opts *clientOpts) {
opts.dialOpts = append(opts.dialOpts, dialOpts...)
}
}
// DefaultClientOpts returns the set of default rpc ClientOpts the draft
// client requires.
func DefaultClientOpts() []ClientOpt {
return []ClientOpt{
WithGrpcDialOpt(
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: time.Duration(30) * time.Second,
}),
grpc.WithTimeout(5*time.Second),
grpc.WithBlock(),
),
}
}
// serverOpts specifies the union of all configurable
// options an rpc.Server accepts, e.g. TLS config.
type serverOpts struct{}
type serverOpts struct {
grpcOpts []grpc.ServerOption
}
// WithGrpcServerOpt adds the provided grpc.ServerOption
// for the rpc.Server to use when initializing the underlying
// grpc.Server.
func WithGrpcServerOpt(grpcOpts ...grpc.ServerOption) ServerOpt {
return func(opts *serverOpts) {
opts.grpcOpts = append(opts.grpcOpts, grpcOpts...)
}
}
// DefaultServerOpts returns the set of default rpc ServerOpts that draftd requires.
func DefaultServerOpts() []ServerOpt {
return []ServerOpt{
WithGrpcServerOpt(grpc.MaxMsgSize(maxMsgSize)),
}
}

Просмотреть файл

@ -30,6 +30,7 @@ type serverImpl struct {
func newServerImpl(opts ...ServerOpt) *serverImpl {
var s serverImpl
opts = append(DefaultServerOpts(), opts...)
for _, opt := range opts {
opt(&s.opts)
}
@ -38,13 +39,9 @@ func newServerImpl(opts ...ServerOpt) *serverImpl {
// Server implements rpc.Server.Serve
func (s *serverImpl) Serve(lis net.Listener, h Handler) error {
var opts = []grpc.ServerOption{
grpc.MaxMsgSize(maxMsgSize),
}
// TODO: If TLS load keys and such
s.h = h
s.lis = lis
s.srv = grpc.NewServer(opts...)
s.srv = grpc.NewServer(s.opts.grpcOpts...)
RegisterDraftServer(s.srv, s)
return s.srv.Serve(s.lis)
}