Skip to content

Commit

Permalink
Add support for ciphersuites in tls config (#3564)
Browse files Browse the repository at this point in the history
* Add support for ciphersuites in tls config

Signed-off-by: Ashmita <ashmita.bohara@logz.io>

* Feedbacks

Signed-off-by: Ashmita152 <ashmita.bohara@logz.io>

* Feedbacks

Signed-off-by: Ashmita152 <ashmita.bohara@logz.io>

* Feedbacks

Signed-off-by: Ashmita152 <ashmita.bohara@logz.io>
  • Loading branch information
Ashmita152 committed Mar 6, 2022
1 parent c156ebd commit df44af3
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 1 deletion.
42 changes: 42 additions & 0 deletions pkg/config/tlscfg/ciphersuites.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2022 The Jaeger Authors.
//
// 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 tlscfg

import (
"crypto/tls"
"fmt"
)

func allCiphers() map[string]uint16 {
acceptedCiphers := make(map[string]uint16)
for _, suite := range tls.CipherSuites() {
acceptedCiphers[suite.Name] = suite.ID
}
return acceptedCiphers
}

// CipherSuiteNamesToIDs returns a list of cipher suite IDs from the cipher suite names passed.
func CipherSuiteNamesToIDs(cipherNames []string) ([]uint16, error) {
var ciphersIDs []uint16
possibleCiphers := allCiphers()
for _, cipher := range cipherNames {
intValue, ok := possibleCiphers[cipher]
if !ok {
return nil, fmt.Errorf("cipher suite %s not supported or doesn't exist", cipher)
}
ciphersIDs = append(ciphersIDs, intValue)
}
return ciphersIDs, nil
}
70 changes: 70 additions & 0 deletions pkg/config/tlscfg/ciphersuites_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) 2022 The Jaeger Authors.
//
// 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 tlscfg

import (
"crypto/tls"
"reflect"
"testing"
)

func TestTLSCipherSuites(t *testing.T) {
tests := []struct {
flag []string
expected []uint16
expectedError bool
}{
{
// Happy case
flag: []string{"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"},
expected: []uint16{tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
expectedError: false,
},
{
// One flag only
flag: []string{"TLS_AES_128_GCM_SHA256"},
expected: []uint16{tls.TLS_AES_128_GCM_SHA256},
expectedError: false,
},
{
// Empty flag
flag: []string{},
expected: nil,
expectedError: false,
},
{
// Duplicated flag
flag: []string{"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"},
expected: []uint16{tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384, tls.TLS_AES_128_GCM_SHA256},
expectedError: false,
},
{
// Invalid flag
flag: []string{"TLS_INVALID_CIPHER_SUITE"},
expected: nil,
expectedError: true,
},
}

for i, test := range tests {
uIntFlags, err := CipherSuiteNamesToIDs(test.flag)
if !reflect.DeepEqual(uIntFlags, test.expected) {
t.Errorf("%d: expected %+v, got %+v", i, test.expected, uIntFlags)
}
if test.expectedError && err == nil {
t.Errorf("%d: expecting error, got %+v", i, err)
}
}
}
9 changes: 9 additions & 0 deletions pkg/config/tlscfg/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package tlscfg

import (
"flag"
"strings"

"github.com/spf13/viper"
)
Expand All @@ -29,6 +30,7 @@ const (
tlsServerName = tlsPrefix + ".server-name"
tlsClientCA = tlsPrefix + ".client-ca"
tlsSkipHostVerify = tlsPrefix + ".skip-host-verify"
tlsCipherSuites = tlsPrefix + ".cipher-suites"
)

// ClientFlagsConfig describes which CLI flags for TLS client should be generated.
Expand Down Expand Up @@ -57,6 +59,7 @@ func (c ServerFlagsConfig) AddFlags(flags *flag.FlagSet) {
flags.String(c.Prefix+tlsCert, "", "Path to a TLS Certificate file, used to identify this server to clients")
flags.String(c.Prefix+tlsKey, "", "Path to a TLS Private Key file, used to identify this server to clients")
flags.String(c.Prefix+tlsClientCA, "", "Path to a TLS CA (Certification Authority) file used to verify certificates presented by clients (if unset, all clients are permitted)")
flags.String(c.Prefix+tlsCipherSuites, "", "Comma-separated list of cipher suites for the server, values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).")
}

// InitFromViper creates tls.Config populated with values retrieved from Viper.
Expand All @@ -78,5 +81,11 @@ func (c ServerFlagsConfig) InitFromViper(v *viper.Viper) Options {
p.CertPath = v.GetString(c.Prefix + tlsCert)
p.KeyPath = v.GetString(c.Prefix + tlsKey)
p.ClientCAPath = v.GetString(c.Prefix + tlsClientCA)
p.CipherSuites = strings.Split(stripWhiteSpace(v.GetString(c.Prefix+tlsCipherSuites)), ",")
return p
}

// stripWhiteSpace removes all whitespace characters from a string
func stripWhiteSpace(str string) string {
return strings.Replace(str, " ", "", -1)
}
2 changes: 2 additions & 0 deletions pkg/config/tlscfg/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func TestServerFlags(t *testing.T) {
"--prefix.tls.enabled=true",
"--prefix.tls.cert=cert-file",
"--prefix.tls.key=key-file",
"--prefix.tls.cipher-suites=TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
}

tests := []struct {
Expand Down Expand Up @@ -107,6 +108,7 @@ func TestServerFlags(t *testing.T) {
CertPath: "cert-file",
KeyPath: "key-file",
ClientCAPath: test.file,
CipherSuites: []string{"TLS_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"},
}, tlsOpts)
})
}
Expand Down
10 changes: 9 additions & 1 deletion pkg/config/tlscfg/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Options struct {
KeyPath string `mapstructure:"key"`
ServerName string `mapstructure:"server_name"` // only for client-side TLS config
ClientCAPath string `mapstructure:"client_ca"` // only for server-side TLS config for client auth
CipherSuites []string `mapstructure:"cipher_suites"`
SkipHostVerify bool `mapstructure:"skip_host_verify"`
certWatcher *certWatcher `mapstructure:"-"`
}
Expand All @@ -41,17 +42,24 @@ var systemCertPool = x509.SystemCertPool // to allow overriding in unit test

// Config loads TLS certificates and returns a TLS Config.
func (p *Options) Config(logger *zap.Logger) (*tls.Config, error) {

certPool, err := p.loadCertPool()
if err != nil {
return nil, fmt.Errorf("failed to load CA CertPool: %w", err)
}

cipherSuiteIds, err := CipherSuiteNamesToIDs(p.CipherSuites)
if err != nil {
return nil, fmt.Errorf("failed to get cipher suite ids from cipher suite names: %w", err)
}

// #nosec G402
tlsCfg := &tls.Config{
RootCAs: certPool,
ServerName: p.ServerName,
InsecureSkipVerify: p.SkipHostVerify,
CipherSuites: cipherSuiteIds,
}

if p.ClientCAPath != "" {
certPool := x509.NewCertPool()
if err := addCertToPool(p.ClientCAPath, certPool); err != nil {
Expand Down
7 changes: 7 additions & 0 deletions pkg/config/tlscfg/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ func TestOptionsToConfig(t *testing.T) {
ClientCAPath: testCertKeyLocation + "/example-CA-cert.pem",
},
},
{
name: "should fail with invalid Cipher Suite",
options: Options{
CipherSuites: []string{"TLS_INVALID_CIPHER_SUITE"},
},
expectError: "failed to get cipher suite ids from cipher suite names: cipher suite TLS_INVALID_CIPHER_SUITE not supported or doesn't exist",
},
}

for _, test := range tests {
Expand Down

0 comments on commit df44af3

Please sign in to comment.