LDAP: Allow setting minimum TLS version and accepted ciphers (#63646)

* update ldap library and use go module path

* add TLS min version and accepted min TLS version

* set default min ver to library default

* set default min ver to library default

* add cipher list to toml

* Update pkg/services/ldap/settings.go

Co-authored-by: Karl Persson <kalle.persson@grafana.com>

* Apply suggestions from code review

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* lint

---------

Co-authored-by: Karl Persson <kalle.persson@grafana.com>
Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
This commit is contained in:
Jo
2023-02-28 11:13:46 +00:00
committed by GitHub
parent 2ee73ad7f9
commit 7e97dbde65
14 changed files with 122 additions and 29 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ package ldap
import (
"strings"
"gopkg.in/ldap.v3"
"github.com/go-ldap/ldap/v3"
)
func IsMemberOf(memberOf []string, group string) bool {
+3 -1
View File
@@ -12,7 +12,7 @@ import (
"strings"
"time"
"gopkg.in/ldap.v3"
"github.com/go-ldap/ldap/v3"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/login"
@@ -130,6 +130,8 @@ func (server *Server) Dial() error {
InsecureSkipVerify: server.Config.SkipVerifySSL,
ServerName: host,
RootCAs: certPool,
MinVersion: server.Config.minTLSVersion,
CipherSuites: server.Config.tlsCiphers,
}
if len(clientCert.Certificate) > 0 {
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"fmt"
"testing"
"github.com/go-ldap/ldap/v3"
"github.com/stretchr/testify/assert"
"gopkg.in/ldap.v3"
)
func TestIsMemberOf(t *testing.T) {
+1 -1
View File
@@ -4,9 +4,9 @@ import (
"errors"
"testing"
"github.com/go-ldap/ldap/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/ldap.v3"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/login"
+1 -1
View File
@@ -3,9 +3,9 @@ package ldap
import (
"testing"
"github.com/go-ldap/ldap/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/ldap.v3"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/login"
+1 -1
View File
@@ -5,9 +5,9 @@ import (
"fmt"
"testing"
"github.com/go-ldap/ldap/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/ldap.v3"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models/roletype"
+84 -12
View File
@@ -1,8 +1,10 @@
package ldap
import (
"crypto/tls"
"fmt"
"os"
"strings"
"sync"
"github.com/BurntSushi/toml"
@@ -21,18 +23,24 @@ type Config struct {
// ServerConfig holds connection data to LDAP
type ServerConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
UseSSL bool `toml:"use_ssl"`
StartTLS bool `toml:"start_tls"`
SkipVerifySSL bool `toml:"ssl_skip_verify"`
RootCACert string `toml:"root_ca_cert"`
ClientCert string `toml:"client_cert"`
ClientKey string `toml:"client_key"`
BindDN string `toml:"bind_dn"`
BindPassword string `toml:"bind_password"`
Timeout int `toml:"timeout"`
Attr AttributeMap `toml:"attributes"`
Host string `toml:"host"`
Port int `toml:"port"`
UseSSL bool `toml:"use_ssl"`
StartTLS bool `toml:"start_tls"`
SkipVerifySSL bool `toml:"ssl_skip_verify"`
MinTLSVersion string `toml:"min_tls_version"`
minTLSVersion uint16 `toml:"-"`
TLSCiphers []string `toml:"tls_ciphers"`
tlsCiphers []uint16 `toml:"-"`
RootCACert string `toml:"root_ca_cert"`
ClientCert string `toml:"client_cert"`
ClientKey string `toml:"client_key"`
BindDN string `toml:"bind_dn"`
BindPassword string `toml:"bind_password"`
Timeout int `toml:"timeout"`
Attr AttributeMap `toml:"attributes"`
SearchFilter string `toml:"search_filter"`
SearchBaseDNs []string `toml:"search_base_dns"`
@@ -135,6 +143,20 @@ func readConfig(configFile string) (*Config, error) {
return nil, fmt.Errorf("%v: %w", "Failed to validate SearchBaseDNs section", err)
}
if server.MinTLSVersion != "" {
server.minTLSVersion, err = tlsNameToVersion(server.MinTLSVersion)
if err != nil {
logger.Error("Failed to set min TLS version. Ignoring", "err", err)
}
}
if len(server.TLSCiphers) > 0 {
server.tlsCiphers, err = tlsCiphersToIDs(server.TLSCiphers)
if err != nil {
logger.Error("Unrecognized TLS Cipher(s). Ignoring", "err", err)
}
}
for _, groupMap := range server.Groups {
if groupMap.OrgRole == "" && groupMap.IsGrafanaAdmin == nil {
return nil, fmt.Errorf("LDAP group mapping: organization role or grafana admin status is required")
@@ -169,3 +191,53 @@ func assertNotEmptyCfg(val interface{}, propName string) error {
}
return nil
}
// tlsNameToVersion converts a string to a tls version
func tlsNameToVersion(name string) (uint16, error) {
name = strings.ToUpper(name)
switch name {
case "TLS1.0":
return tls.VersionTLS10, nil
case "TLS1.1":
return tls.VersionTLS11, nil
case "TLS1.2":
return tls.VersionTLS12, nil
case "TLS1.3":
return tls.VersionTLS13, nil
}
return 0, fmt.Errorf("unknown tls version: %q", name)
}
// Cipher strings https://go.dev/src/crypto/tls/cipher_suites.go
// Ex: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" or "TLS_RSA_WITH_AES_128_CBC_SHA"
func tlsCiphersToIDs(names []string) ([]uint16, error) {
if len(names) == 0 || names == nil {
// no ciphers specified, use defaults
return nil, nil
}
var ids []uint16
var missing []string
ciphers := tls.CipherSuites()
var cipherMap = make(map[string]uint16, len(ciphers))
for _, cipher := range ciphers {
cipherMap[cipher.Name] = cipher.ID
}
for _, name := range names {
name = strings.ToUpper(name)
id, ok := cipherMap[name]
if !ok {
missing = append(missing, name)
continue
}
ids = append(ids, id)
}
if len(missing) > 0 {
return ids, fmt.Errorf("unknown ciphers: %v", missing)
}
return ids, nil
}
+6 -3
View File
@@ -1,7 +1,7 @@
package ldap
import (
"os"
"crypto/tls"
"testing"
"github.com/stretchr/testify/assert"
@@ -12,11 +12,14 @@ func TestReadingLDAPSettings(t *testing.T) {
config, err := readConfig("testdata/ldap.toml")
assert.Nil(t, err, "No error when reading ldap config")
assert.EqualValues(t, "127.0.0.1", config.Servers[0].Host)
assert.EqualValues(t, "tls1.3", config.Servers[0].MinTLSVersion)
assert.EqualValues(t, uint16(tls.VersionTLS13), config.Servers[0].minTLSVersion)
assert.EqualValues(t, []string{"TLS_CHACHA20_POLY1305_SHA256", "TLS_AES_128_GCM_SHA256"}, config.Servers[0].TLSCiphers)
assert.ElementsMatch(t, []uint16{tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_AES_128_GCM_SHA256}, config.Servers[0].tlsCiphers)
}
func TestReadingLDAPSettingsWithEnvVariable(t *testing.T) {
err := os.Setenv("ENV_PASSWORD", "MySecret")
require.NoError(t, err)
t.Setenv("ENV_PASSWORD", "MySecret")
config, err := readConfig("testdata/ldap.toml")
require.NoError(t, err)
+2 -1
View File
@@ -4,6 +4,8 @@ port = 389
use_ssl = false
start_tls = false
ssl_skip_verify = false
tls_ciphers = ["TLS_CHACHA20_POLY1305_SHA256", "TLS_AES_128_GCM_SHA256"]
min_tls_version = "tls1.3"
bind_dn = "cn=admin,dc=grafana,dc=org"
bind_password = '${ENV_PASSWORD}'
search_filter = "(cn=%s)"
@@ -24,4 +26,3 @@ grafana_admin = true
[[servers.group_mappings]]
group_dn = "cn=users,ou=groups,dc=grafana,dc=org"
org_role = "Editor"
+1 -1
View File
@@ -3,7 +3,7 @@ package ldap
import (
"crypto/tls"
"gopkg.in/ldap.v3"
"github.com/go-ldap/ldap/v3"
//TODO(sh0rez): remove once import cycle resolved
_ "github.com/grafana/grafana/pkg/api/response"