Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Commit

Permalink
Merge pull request #242 from maurosr/tls
Browse files Browse the repository at this point in the history
Thank you!
  • Loading branch information
Shlomi Noach authored Aug 1, 2017
2 parents f1959f9 + 3068a6b commit 2cb8474
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 57 deletions.
4 changes: 4 additions & 0 deletions go/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ type Configuration struct {
MySQLTopologySSLCAFile string // Certificate Authority PEM file used to authenticate with a Topology mysql instance with TLS
MySQLTopologySSLSkipVerify bool // If true, do not strictly validate mutual TLS certs for Topology mysql instances
MySQLTopologyUseMutualTLS bool // Turn on TLS authentication with the Topology MySQL instances
MySQLTopologyUseMixedTLS bool // Mixed TLS and non-TLS authentication with the Topology MySQL instances
TLSCacheTTLFactor uint // Factor of InstancePollSeconds that we set as TLS info cache expiracy
BackendDB string // EXPERIMENTAL: type of backend db; either "mysql" or "sqlite3"
SQLite3DataFile string // when BackendDB == "sqlite3", full path to sqlite3 datafile
SkipOrchestratorDatabaseUpdate bool // When true, do not check backend database schema nor attempt to update it. Useful when you may be running multiple versions of orchestrator, and you only wish certain boxes to dictate the db structure (or else any time a different orchestrator version runs it will rebuild database schema)
Expand Down Expand Up @@ -258,13 +260,15 @@ func newConfiguration() *Configuration {
MySQLOrchestratorMaxPoolConnections: 128, // limit concurrent conns to backend DB
MySQLOrchestratorPort: 3306,
MySQLTopologyUseMutualTLS: false,
MySQLTopologyUseMixedTLS: true,
MySQLOrchestratorUseMutualTLS: false,
MySQLConnectTimeoutSeconds: 2,
MySQLOrchestratorReadTimeoutSeconds: 30,
MySQLDiscoveryReadTimeoutSeconds: 10,
MySQLTopologyReadTimeoutSeconds: 600,
MySQLInterpolateParams: false,
DefaultInstancePort: 3306,
TLSCacheTTLFactor: 100,
InstancePollSeconds: 5,
InstanceWriteBufferSize: 100,
BufferInstanceWrites: false,
Expand Down
71 changes: 14 additions & 57 deletions go/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,11 @@
package db

import (
"crypto/tls"
"database/sql"
"fmt"
"strings"

"github.com/github/orchestrator/go/config"
"github.com/github/orchestrator/go/ssl"
"github.com/go-sql-driver/mysql"
"github.com/openark/golib/log"
"github.com/openark/golib/sqlutils"
)
Expand Down Expand Up @@ -783,6 +780,14 @@ var generateSQLBase = []string{
PRIMARY KEY (recovery_step_id)
) ENGINE=InnoDB DEFAULT CHARSET=ascii
`,
`
CREATE TABLE IF NOT EXISTS database_instance_tls (
hostname varchar(128) CHARACTER SET ascii NOT NULL,
port smallint(5) unsigned NOT NULL,
required tinyint unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (hostname,port)
) ENGINE=InnoDB DEFAULT CHARSET=ascii
`,
}

// generateSQLPatches contains DDLs for patching schema to the latest version.
Expand Down Expand Up @@ -1253,12 +1258,6 @@ var generateSQLPatches = []string{
`,
}

// Track if a TLS has already been configured for topology
var topologyTLSConfigured bool = false

// Track if a TLS has already been configured for Orchestrator
var orchestratorTLSConfigured bool = false

// OpenDiscovery returns a DB instance to access a topology instance.
// It has lower read timeout than OpenTopology and is intended to
// be used with low-latency discovery queries.
Expand All @@ -1279,39 +1278,20 @@ func openTopology(host string, port int, readTimeout int) (*sql.DB, error) {
config.Config.MySQLConnectTimeoutSeconds,
readTimeout,
)
if config.Config.MySQLTopologyUseMutualTLS {

if config.Config.MySQLTopologyUseMutualTLS ||
(config.Config.MySQLTopologyUseMixedTLS && requiresTLS(host, port, mysql_uri)) {
mysql_uri, _ = SetupMySQLTopologyTLS(mysql_uri)
}
db, _, err := sqlutils.GetDB(mysql_uri)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(config.MySQLTopologyMaxPoolConnections)
db.SetMaxIdleConns(config.MySQLTopologyMaxPoolConnections)
return db, err
}

// Create a TLS configuration from the config supplied CA, Certificate, and Private key.
// Register the TLS config with the mysql drivers as the "topology" config
// Modify the supplied URI to call the TLS config
// TODO: Way to have password mixed with TLS for various nodes in the topology. Currently everything is TLS or everything is password
func SetupMySQLTopologyTLS(uri string) (string, error) {
if !topologyTLSConfigured {
tlsConfig, err := ssl.NewTLSConfig(config.Config.MySQLTopologySSLCAFile, !config.Config.MySQLTopologySSLSkipVerify)
// Drop to TLS 1.0 for talking to MySQL
tlsConfig.MinVersion = tls.VersionTLS10
if err != nil {
return "", log.Fatalf("Can't create TLS configuration for Topology connection %s: %s", uri, err)
}
tlsConfig.InsecureSkipVerify = config.Config.MySQLTopologySSLSkipVerify
if err = ssl.AppendKeyPair(tlsConfig, config.Config.MySQLTopologySSLCertFile, config.Config.MySQLTopologySSLPrivateKeyFile); err != nil {
return "", log.Fatalf("Can't setup TLS key pairs for %s: %s", uri, err)
}
if err = mysql.RegisterTLSConfig("topology", tlsConfig); err != nil {
return "", log.Fatalf("Can't register mysql TLS config for topology: %s", err)
}
topologyTLSConfigured = true
}
return fmt.Sprintf("%s&tls=topology", uri), nil
}

// OpenTopology returns the DB instance for the orchestrator backed database
func OpenOrchestrator() (db *sql.DB, err error) {
var fromCache bool
Expand Down Expand Up @@ -1411,29 +1391,6 @@ func registerOrchestratorDeployment(db *sql.DB) error {
return nil
}

// Create a TLS configuration from the config supplied CA, Certificate, and Private key.
// Register the TLS config with the mysql drivers as the "orchestrator" config
// Modify the supplied URI to call the TLS config
func SetupMySQLOrchestratorTLS(uri string) (string, error) {
if !orchestratorTLSConfigured {
tlsConfig, err := ssl.NewTLSConfig(config.Config.MySQLOrchestratorSSLCAFile, true)
// Drop to TLS 1.0 for talking to MySQL
tlsConfig.MinVersion = tls.VersionTLS10
if err != nil {
return "", log.Fatalf("Can't create TLS configuration for Orchestrator connection %s: %s", uri, err)
}
tlsConfig.InsecureSkipVerify = config.Config.MySQLOrchestratorSSLSkipVerify
if err = ssl.AppendKeyPair(tlsConfig, config.Config.MySQLOrchestratorSSLCertFile, config.Config.MySQLOrchestratorSSLPrivateKeyFile); err != nil {
return "", log.Fatalf("Can't setup TLS key pairs for %s: %s", uri, err)
}
if err = mysql.RegisterTLSConfig("orchestrator", tlsConfig); err != nil {
return "", log.Fatalf("Can't register mysql TLS config for orchestrator: %s", err)
}
orchestratorTLSConfigured = true
}
return fmt.Sprintf("%s&tls=orchestrator", uri), nil
}

// deployStatements will issue given sql queries that are not already known to be deployed.
// This iterates both lists (to-run and already-deployed) and also verifies no contraditions.
func deployStatements(db *sql.DB, queries []string) error {
Expand Down
172 changes: 172 additions & 0 deletions go/db/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
Copyright 2014 Outbrain Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package db

import (
"crypto/tls"
"fmt"
"strings"
"time"

"github.com/go-sql-driver/mysql"
"github.com/openark/golib/log"
"github.com/openark/golib/sqlutils"
"github.com/patrickmn/go-cache"
"github.com/rcrowley/go-metrics"

"github.com/github/orchestrator/go/config"
"github.com/github/orchestrator/go/ssl"
)

const Error3159 = "Error 3159: Connections using insecure transport are prohibited while --require_secure_transport=ON."

// Track if a TLS has already been configured for topology
var topologyTLSConfigured bool = false

// Track if a TLS has already been configured for Orchestrator
var orchestratorTLSConfigured bool = false

var requireTLSCache *cache.Cache = cache.New(time.Duration(config.Config.TLSCacheTTLFactor*config.Config.InstancePollSeconds)*time.Second, time.Second)

var readInstanceTLSCounter = metrics.NewCounter()
var writeInstanceTLSCounter = metrics.NewCounter()
var readInstanceTLSCacheCounter = metrics.NewCounter()
var writeInstanceTLSCacheCounter = metrics.NewCounter()

func init() {
metrics.Register("instance_tls.read", readInstanceTLSCounter)
metrics.Register("instance_tls.write", writeInstanceTLSCounter)
metrics.Register("instance_tls.read_cache", readInstanceTLSCacheCounter)
metrics.Register("instance_tls.write_cache", writeInstanceTLSCacheCounter)
}

func requiresTLS(host string, port int, mysql_uri string) bool {
var required int = 0
var first_time bool = true
var found bool = false
var value interface{}

if value, found = requireTLSCache.Get(fmt.Sprintf("%s:%d", host, port)); found {
required = value.(int)
readInstanceTLSCacheCounter.Inc(1)
}

if !found {
query := `
select
required
from
database_instance_tls
where
hostname = ?
and port = ?
`
err := QueryOrchestrator(query, sqlutils.Args(host, port), func(m sqlutils.RowMap) error {
required = m.GetInt("required")
first_time = false
return nil
})
if err != nil {
log.Errore(err)
return required != 0
}

readInstanceTLSCounter.Inc(1)

if first_time {
db, _, _ := sqlutils.GetDB(mysql_uri)
err = db.Ping()
if err != nil && strings.Contains(err.Error(), Error3159) {
required = 1
} else {
required = 0
}

insert := `
insert into
database_instance_tls
set
hostname = ?,
port = ?,
required = ?
`
_, err = ExecOrchestrator(insert, host, port, required)
if err != nil {
log.Errore(err)
return required != 0
}

writeInstanceTLSCounter.Inc(1)
}

requireTLSCache.Set(fmt.Sprintf("%s:%d", host, port), required, cache.DefaultExpiration)
writeInstanceTLSCacheCounter.Inc(1)
}

return required != 0
}

// Create a TLS configuration from the config supplied CA, Certificate, and Private key.
// Register the TLS config with the mysql drivers as the "topology" config
// Modify the supplied URI to call the TLS config
func SetupMySQLTopologyTLS(uri string) (string, error) {
if !topologyTLSConfigured {
tlsConfig, err := ssl.NewTLSConfig(config.Config.MySQLTopologySSLCAFile, !config.Config.MySQLTopologySSLSkipVerify)
// Drop to TLS 1.0 for talking to MySQL
tlsConfig.MinVersion = tls.VersionTLS10
if err != nil {
return "", log.Fatalf("Can't create TLS configuration for Topology connection %s: %s", uri, err)
}
tlsConfig.InsecureSkipVerify = config.Config.MySQLTopologySSLSkipVerify

if config.Config.MySQLTopologyUseMutualTLS ||
config.Config.MySQLTopologySSLCertFile != "" ||
config.Config.MySQLTopologySSLPrivateKeyFile != "" {
if err = ssl.AppendKeyPair(tlsConfig, config.Config.MySQLTopologySSLCertFile, config.Config.MySQLTopologySSLPrivateKeyFile); err != nil {
return "", log.Fatalf("Can't setup TLS key pairs for %s: %s", uri, err)
}
}
if err = mysql.RegisterTLSConfig("topology", tlsConfig); err != nil {
return "", log.Fatalf("Can't register mysql TLS config for topology: %s", err)
}
topologyTLSConfigured = true
}
return fmt.Sprintf("%s&tls=topology", uri), nil
}

// Create a TLS configuration from the config supplied CA, Certificate, and Private key.
// Register the TLS config with the mysql drivers as the "orchestrator" config
// Modify the supplied URI to call the TLS config
func SetupMySQLOrchestratorTLS(uri string) (string, error) {
if !orchestratorTLSConfigured {
tlsConfig, err := ssl.NewTLSConfig(config.Config.MySQLOrchestratorSSLCAFile, true)
// Drop to TLS 1.0 for talking to MySQL
tlsConfig.MinVersion = tls.VersionTLS10
if err != nil {
return "", log.Fatalf("Can't create TLS configuration for Orchestrator connection %s: %s", uri, err)
}
tlsConfig.InsecureSkipVerify = config.Config.MySQLOrchestratorSSLSkipVerify
if err = ssl.AppendKeyPair(tlsConfig, config.Config.MySQLOrchestratorSSLCertFile, config.Config.MySQLOrchestratorSSLPrivateKeyFile); err != nil {
return "", log.Fatalf("Can't setup TLS key pairs for %s: %s", uri, err)
}
if err = mysql.RegisterTLSConfig("orchestrator", tlsConfig); err != nil {
return "", log.Fatalf("Can't register mysql TLS config for orchestrator: %s", err)
}
orchestratorTLSConfigured = true
}
return fmt.Sprintf("%s&tls=orchestrator", uri), nil
}

0 comments on commit 2cb8474

Please sign in to comment.