diff --git a/lib/client/interfaces.go b/lib/client/interfaces.go index e0f8193e54d68..13b17aff1d776 100644 --- a/lib/client/interfaces.go +++ b/lib/client/interfaces.go @@ -57,6 +57,14 @@ type Key struct { ClusterName string } +// TLSCAs returns all TLS CA certificates from this key +func (k *Key) TLSCAs() (result [][]byte) { + for _, ca := range k.TrustedCA { + result = append(result, ca.TLSCertificates...) + } + return result +} + // TLSConfig returns client TLS configuration used // to authenticate against API servers func (k *Key) ClientTLSConfig() (*tls.Config, error) { diff --git a/lib/utils/addr.go b/lib/utils/addr.go index 3ef4397bbc208..5708753cc5a37 100644 --- a/lib/utils/addr.go +++ b/lib/utils/addr.go @@ -137,6 +137,18 @@ func (a *NetAddr) Set(s string) error { return nil } +// ParseAddrs parses the provided slice of strings as a slice of NetAddr's. +func ParseAddrs(addrs []string) (result []NetAddr, err error) { + for _, addr := range addrs { + parsed, err := ParseAddr(addr) + if err != nil { + return nil, trace.Wrap(err) + } + result = append(result, *parsed) + } + return result, nil +} + // ParseAddr takes strings like "tcp://host:port/path" and returns // *NetAddr or an error func ParseAddr(a string) (*NetAddr, error) { diff --git a/lib/utils/addr_test.go b/lib/utils/addr_test.go index 67f140ad6c64d..1caf1a74b8863 100644 --- a/lib/utils/addr_test.go +++ b/lib/utils/addr_test.go @@ -288,3 +288,28 @@ func (s *AddrTestSuite) TestUnmarshal(c *C) { Commentf("test case %v, %v should be unmarshalled to: %v", i, testCase.in, testCase.expected)) } } + +func (s *AddrTestSuite) TestParseMultiple(c *C) { + tests := []struct { + in []string + out []NetAddr + }{ + { + in: []string{ + "https://localhost:3080", + "tcp://example:587/path", + "[::1]:465", + }, + out: []NetAddr{ + {Addr: "localhost:3080", AddrNetwork: "https"}, + {Addr: "example:587", AddrNetwork: "tcp", Path: "/path"}, + {Addr: "[::1]:465", AddrNetwork: "tcp"}, + }, + }, + } + for _, test := range tests { + parsed, err := ParseAddrs(test.in) + c.Assert(err, IsNil) + c.Assert(parsed, DeepEquals, test.out) + } +} diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go index aec12bca83ba4..bb5f6be9f4f14 100644 --- a/tool/tctl/common/tctl.go +++ b/tool/tctl/common/tctl.go @@ -27,6 +27,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/tool/tsh/common" "github.com/gravitational/kingpin" "github.com/gravitational/trace" @@ -35,9 +36,16 @@ import ( // GlobalCLIFlags keeps the CLI flags that apply to all tctl commands type GlobalCLIFlags struct { - Debug bool - ConfigFile string + // Debug enables verbose logging mode to the console + Debug bool + // ConfigFile is the path to the Teleport configuration file + ConfigFile string + // ConfigString is the base64-encoded string with Teleport configuration ConfigString string + // AuthServerAddr lists addresses of auth servers to connect to + AuthServerAddr []string + // IdentityFilePath is the path to the identity file + IdentityFilePath string } // CLICommand interface must be implemented by every CLI command @@ -85,6 +93,13 @@ func Run(commands []CLICommand) { ExistingFileVar(&ccf.ConfigFile) app.Flag("config-string", "Base64 encoded configuration string").Hidden().Envar(defaults.ConfigEnvar).StringVar(&ccf.ConfigString) + app.Flag("auth-server", + fmt.Sprintf("Address of the auth server [%v]. Can be supplied multiple times", defaults.AuthConnectAddr().Addr)). + Default(defaults.AuthConnectAddr().Addr). + StringsVar(&ccf.AuthServerAddr) + app.Flag("identity", "Path to the identity file exported with 'tctl auth sign'"). + Short('i'). + StringVar(&ccf.IdentityFilePath) // "version" command is always available: ver := app.Command("version", "Print cluster version") @@ -133,19 +148,19 @@ func connectToAuthService(cfg *service.Config) (client auth.ClientI, err error) *defaults.AuthConnectAddr(), } } - // read the host SSH keys and use them to open an SSH connection to the auth service - i, err := auth.ReadLocalIdentity(filepath.Join(cfg.DataDir, teleport.ComponentProcess), auth.IdentityID{Role: teleport.RoleAdmin, HostUUID: cfg.HostUUID}) + + identity, err := getIdentity(cfg) if err != nil { - // the "admin" identity is not present? this means the tctl is running NOT on the auth server. - if trace.IsNotFound(err) { - return nil, trace.AccessDenied("tctl must be used on the auth server") - } return nil, trace.Wrap(err) } - tlsConfig, err := i.TLSConfig(cfg.CipherSuites) + + tlsConfig, err := identity.TLSConfig(cfg.CipherSuites) if err != nil { return nil, trace.Wrap(err) } + + logrus.Debugf("Connecting to auth servers: %v.", cfg.AuthServers) + client, err = auth.NewTLSClient(auth.ClientConfig{Addrs: cfg.AuthServers, TLS: tlsConfig}) if err != nil { return nil, trace.Wrap(err) @@ -162,6 +177,27 @@ func connectToAuthService(cfg *service.Config) (client auth.ClientI, err error) return client, nil } +// getIdentity returns auth.Identity to use when connecting to auth server +func getIdentity(cfg *service.Config) (*auth.Identity, error) { + // If identity was provided in the configuration, use it + if len(cfg.Identities) != 0 { + return cfg.Identities[0], nil + } + // Otherwise, assume we're running on the auth node and read the host-local + // identity from Teleport data directory + identity, err := auth.ReadLocalIdentity(filepath.Join(cfg.DataDir, teleport.ComponentProcess), auth.IdentityID{Role: teleport.RoleAdmin, HostUUID: cfg.HostUUID}) + if err != nil { + // The "admin" identity is not present? This means the tctl is running + // NOT on the auth server + if trace.IsNotFound(err) { + return nil, trace.AccessDenied("tctl must be either used on the auth " + + "server or provided with the identity file via --identity flag") + } + return nil, trace.Wrap(err) + } + return identity, nil +} + // applyConfig takes configuration values from the config file and applies // them to 'service.Config' object func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) error { @@ -187,11 +223,31 @@ func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) error { utils.InitLogger(utils.LoggingForCLI, logrus.DebugLevel) logrus.Debugf("DEBUG logging enabled") } - - // read a host UUID for this node - cfg.HostUUID, err = utils.ReadHostUUID(cfg.DataDir) - if err != nil { - utils.FatalError(err) + // --auth-server flag(-s) + if len(ccf.AuthServerAddr) != 0 { + cfg.AuthServers, err = utils.ParseAddrs(ccf.AuthServerAddr) + if err != nil { + return trace.Wrap(err) + } + } + // --identity flag + if ccf.IdentityFilePath != "" { + key, _, err := common.LoadIdentity(ccf.IdentityFilePath) + if err != nil { + return trace.Wrap(err) + } + identity, err := auth.ReadTLSIdentityFromKeyPair(key.Priv, key.TLSCert, key.TLSCAs()) + if err != nil { + return trace.Wrap(err) + } + cfg.Identities = append(cfg.Identities, identity) + } else { + // read the host UUID only in case the identity was not provided, + // because it will be used for reading local auth server identity + cfg.HostUUID, err = utils.ReadHostUUID(cfg.DataDir) + if err != nil { + return trace.Wrap(err) + } } return nil } diff --git a/tool/tsh/common/identity.go b/tool/tsh/common/identity.go new file mode 100644 index 0000000000000..9eeba6250bb62 --- /dev/null +++ b/tool/tsh/common/identity.go @@ -0,0 +1,219 @@ +/* +Copyright 2019 Gravitational, 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 common + +import ( + "bufio" + "bytes" + "crypto/tls" + "crypto/x509" + "io" + "io/ioutil" + "net" + "os" + + "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/sshutils" + + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" +) + +// LoadIdentity loads the private key + certificate from a file +// Returns: +// - client key: user's private key+cert +// - host auth callback: function to validate the host (may be null) +// - error, if something happens when reading the identity file +// +// If the "host auth callback" is not returned, user will be prompted to +// trust the proxy server. +func LoadIdentity(idFn string) (*client.Key, ssh.HostKeyCallback, error) { + logrus.Infof("Reading identity file: %v", idFn) + + f, err := os.Open(idFn) + if err != nil { + return nil, nil, trace.Wrap(err) + } + defer f.Close() + ident, err := decodeIdentity(f) + if err != nil { + return nil, nil, trace.Wrap(err, "failed to parse identity file") + } + // did not find the certificate in the file? look in a separate file with + // -cert.pub prefix + if len(ident.Certs.SSH) == 0 { + certFn := idFn + "-cert.pub" + logrus.Infof("Certificate not found in %s. Looking in %s.", idFn, certFn) + ident.Certs.SSH, err = ioutil.ReadFile(certFn) + if err != nil { + return nil, nil, trace.Wrap(err) + } + } + // validate both by parsing them: + privKey, err := ssh.ParseRawPrivateKey(ident.PrivateKey) + if err != nil { + return nil, nil, trace.BadParameter("invalid identity: %s. %v", idFn, err) + } + signer, err := ssh.NewSignerFromKey(privKey) + if err != nil { + return nil, nil, trace.Wrap(err) + } + // validate TLS Cert (if present): + if len(ident.Certs.TLS) > 0 { + _, err := tls.X509KeyPair(ident.Certs.TLS, ident.PrivateKey) + if err != nil { + return nil, nil, trace.Wrap(err) + } + } + // Validate TLS CA certs (if present). + var trustedCA []auth.TrustedCerts + if len(ident.CACerts.TLS) > 0 { + var trustedCerts auth.TrustedCerts + pool := x509.NewCertPool() + for i, certPEM := range ident.CACerts.TLS { + if !pool.AppendCertsFromPEM(certPEM) { + return nil, nil, trace.BadParameter("identity file contains invalid TLS CA cert (#%v)", i+1) + } + trustedCerts.TLSCertificates = append(trustedCerts.TLSCertificates, certPEM) + } + trustedCA = []auth.TrustedCerts{trustedCerts} + } + var hostAuthFunc ssh.HostKeyCallback = nil + // validate CA (cluster) cert + if len(ident.CACerts.SSH) > 0 { + var trustedKeys []ssh.PublicKey + for _, caCert := range ident.CACerts.SSH { + _, _, publicKey, _, _, err := ssh.ParseKnownHosts(caCert) + if err != nil { + return nil, nil, trace.BadParameter("CA cert parsing error: %v. cert line :%v", + err.Error(), string(caCert)) + } + trustedKeys = append(trustedKeys, publicKey) + } + + // found CA cert in the indentity file? construct the host key checking function + // and return it: + hostAuthFunc = func(host string, a net.Addr, hostKey ssh.PublicKey) error { + clusterCert, ok := hostKey.(*ssh.Certificate) + if ok { + hostKey = clusterCert.SignatureKey + } + for _, trustedKey := range trustedKeys { + if sshutils.KeysEqual(trustedKey, hostKey) { + return nil + } + } + err = trace.AccessDenied("host %v is untrusted", host) + logrus.Error(err) + return err + } + } + return &client.Key{ + Priv: ident.PrivateKey, + Pub: signer.PublicKey().Marshal(), + Cert: ident.Certs.SSH, + TLSCert: ident.Certs.TLS, + TrustedCA: trustedCA, + }, hostAuthFunc, nil +} + +// rawIdentity encodes the basic components of an identity file. +type rawIdentity struct { + PrivateKey []byte + Certs struct { + SSH []byte + TLS []byte + } + CACerts struct { + SSH [][]byte + TLS [][]byte + } +} + +// decodeIdentity attempts to break up the contents of an identity file +// into its respective components. +func decodeIdentity(r io.Reader) (*rawIdentity, error) { + scanner := bufio.NewScanner(r) + var ident rawIdentity + // Subslice of scanner's buffer pointing to current line + // with leading and trailing whitespace trimmed. + var line []byte + // Attempt to scan to the next line. + scanln := func() bool { + if !scanner.Scan() { + line = nil + return false + } + line = bytes.TrimSpace(scanner.Bytes()) + return true + } + // Check if the current line starts with prefix `p`. + peekln := func(p string) bool { + return bytes.HasPrefix(line, []byte(p)) + } + // Get an "owned" copy of the current line. + cloneln := func() []byte { + ln := make([]byte, len(line)) + copy(ln, line) + return ln + } + // Scan through all lines of identity file. Lines with a known prefix + // are copied out of the scanner's buffer. All others are ignored. + for scanln() { + switch { + case peekln("ssh"): + ident.Certs.SSH = cloneln() + case peekln("@cert-authority"): + ident.CACerts.SSH = append(ident.CACerts.SSH, cloneln()) + case peekln("-----BEGIN"): + // Current line marks the beginning of a PEM block. Consume all + // lines until a corresponding END is found. + var pemBlock []byte + for { + pemBlock = append(pemBlock, line...) + pemBlock = append(pemBlock, '\n') + if peekln("-----END") { + break + } + if !scanln() { + // If scanner has terminated in the middle of a PEM block, either + // the reader encountered an error, or the PEM block is a fragment. + if err := scanner.Err(); err != nil { + return nil, trace.Wrap(err) + } + return nil, trace.BadParameter("invalid PEM block (fragment)") + } + } + // Decide where to place the pem block based on + // which pem blocks have already been found. + switch { + case ident.PrivateKey == nil: + ident.PrivateKey = pemBlock + case ident.Certs.TLS == nil: + ident.Certs.TLS = pemBlock + default: + ident.CACerts.TLS = append(ident.CACerts.TLS, pemBlock) + } + } + } + if err := scanner.Err(); err != nil { + return nil, trace.Wrap(err) + } + return &ident, nil +} diff --git a/tool/tsh/tsh.go b/tool/tsh/tsh.go index 9bfe4119bff47..6b1c07b97f81a 100644 --- a/tool/tsh/tsh.go +++ b/tool/tsh/tsh.go @@ -17,14 +17,9 @@ limitations under the License. package main import ( - "bufio" - "bytes" "context" - "crypto/tls" - "crypto/x509" "fmt" "io" - "io/ioutil" "net" "os" "os/signal" @@ -39,7 +34,6 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/asciitable" - "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/defaults" kubeclient "github.com/gravitational/teleport/lib/kube/client" @@ -47,6 +41,7 @@ import ( "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/tool/tsh/common" "github.com/gravitational/kingpin" "github.com/gravitational/trace" @@ -882,7 +877,7 @@ func makeClient(cf *CLIConf, useProfileLogin bool) (tc *client.TeleportClient, e hostAuthFunc ssh.HostKeyCallback ) // read the ID file and create an "auth method" from it: - key, hostAuthFunc, err = loadIdentity(cf.IdentityFileIn) + key, hostAuthFunc, err = common.LoadIdentity(cf.IdentityFileIn) if err != nil { return nil, trace.Wrap(err) } @@ -1033,189 +1028,6 @@ func refuseArgs(command string, args []string) { } } -// rawIdentity encodes the basic components of an identity file. -type rawIdentity struct { - PrivateKey []byte - Certs struct { - SSH []byte - TLS []byte - } - CACerts struct { - SSH [][]byte - TLS [][]byte - } -} - -// decodeIdentity attempts to break up the contents of an identity file -// into its respective components. -func decodeIdentity(r io.Reader) (*rawIdentity, error) { - scanner := bufio.NewScanner(r) - var ident rawIdentity - // Subslice of scanner's buffer pointing to current line - // with leading and trailing whitespace trimmed. - var line []byte - // Attempt to scan to the next line. - scanln := func() bool { - if !scanner.Scan() { - line = nil - return false - } - line = bytes.TrimSpace(scanner.Bytes()) - return true - } - // Check if the current line starts with prefix `p`. - peekln := func(p string) bool { - return bytes.HasPrefix(line, []byte(p)) - } - // Get an "owned" copy of the current line. - cloneln := func() []byte { - ln := make([]byte, len(line)) - copy(ln, line) - return ln - } - // Scan through all lines of identity file. Lines with a known prefix - // are copied out of the scanner's buffer. All others are ignored. - for scanln() { - switch { - case peekln("ssh"): - ident.Certs.SSH = cloneln() - case peekln("@cert-authority"): - ident.CACerts.SSH = append(ident.CACerts.SSH, cloneln()) - case peekln("-----BEGIN"): - // Current line marks the beginning of a PEM block. Consume all - // lines until a corresponding END is found. - var pemBlock []byte - for { - pemBlock = append(pemBlock, line...) - pemBlock = append(pemBlock, '\n') - if peekln("-----END") { - break - } - if !scanln() { - // If scanner has terminated in the middle of a PEM block, either - // the reader encountered an error, or the PEM block is a fragment. - if err := scanner.Err(); err != nil { - return nil, trace.Wrap(err) - } - return nil, trace.BadParameter("invalid PEM block (fragment)") - } - } - // Decide where to place the pem block based on - // which pem blocks have already been found. - switch { - case ident.PrivateKey == nil: - ident.PrivateKey = pemBlock - case ident.Certs.TLS == nil: - ident.Certs.TLS = pemBlock - default: - ident.CACerts.TLS = append(ident.CACerts.TLS, pemBlock) - } - } - } - if err := scanner.Err(); err != nil { - return nil, trace.Wrap(err) - } - return &ident, nil -} - -// loadIdentity loads the private key + certificate from a file -// Returns: -// - client key: user's private key+cert -// - host auth callback: function to validate the host (may be null) -// - error, if somthing happens when reading the identityf file -// -// If the "host auth callback" is not returned, user will be prompted to -// trust the proxy server. -func loadIdentity(idFn string) (*client.Key, ssh.HostKeyCallback, error) { - log.Infof("Reading identity file: %v", idFn) - - f, err := os.Open(idFn) - if err != nil { - return nil, nil, trace.Wrap(err) - } - defer f.Close() - ident, err := decodeIdentity(f) - if err != nil { - return nil, nil, trace.Wrap(err, "failed to parse identity file") - } - // did not find the certificate in the file? look in a separate file with - // -cert.pub prefix - if len(ident.Certs.SSH) == 0 { - certFn := idFn + "-cert.pub" - log.Infof("Certificate not found in %s. Looking in %s.", idFn, certFn) - ident.Certs.SSH, err = ioutil.ReadFile(certFn) - if err != nil { - return nil, nil, trace.Wrap(err) - } - } - // validate both by parsing them: - privKey, err := ssh.ParseRawPrivateKey(ident.PrivateKey) - if err != nil { - return nil, nil, trace.BadParameter("invalid identity: %s. %v", idFn, err) - } - signer, err := ssh.NewSignerFromKey(privKey) - if err != nil { - return nil, nil, trace.Wrap(err) - } - // validate TLS Cert (if present): - if len(ident.Certs.TLS) > 0 { - _, err := tls.X509KeyPair(ident.Certs.TLS, ident.PrivateKey) - if err != nil { - return nil, nil, trace.Wrap(err) - } - } - // Validate TLS CA certs (if present). - var trustedCA []auth.TrustedCerts - if len(ident.CACerts.TLS) > 0 { - var trustedCerts auth.TrustedCerts - pool := x509.NewCertPool() - for i, certPEM := range ident.CACerts.TLS { - if !pool.AppendCertsFromPEM(certPEM) { - return nil, nil, trace.BadParameter("identity file contains invalid TLS CA cert (#%v)", i+1) - } - trustedCerts.TLSCertificates = append(trustedCerts.TLSCertificates, certPEM) - } - trustedCA = []auth.TrustedCerts{trustedCerts} - } - var hostAuthFunc ssh.HostKeyCallback = nil - // validate CA (cluster) cert - if len(ident.CACerts.SSH) > 0 { - var trustedKeys []ssh.PublicKey - for _, caCert := range ident.CACerts.SSH { - _, _, publicKey, _, _, err := ssh.ParseKnownHosts(caCert) - if err != nil { - return nil, nil, trace.BadParameter("CA cert parsing error: %v. cert line :%v", - err.Error(), string(caCert)) - } - trustedKeys = append(trustedKeys, publicKey) - } - - // found CA cert in the indentity file? construct the host key checking function - // and return it: - hostAuthFunc = func(host string, a net.Addr, hostKey ssh.PublicKey) error { - clusterCert, ok := hostKey.(*ssh.Certificate) - if ok { - hostKey = clusterCert.SignatureKey - } - for _, trustedKey := range trustedKeys { - if sshutils.KeysEqual(trustedKey, hostKey) { - return nil - } - } - err = trace.AccessDenied("host %v is untrusted", host) - log.Error(err) - return err - } - } - return &client.Key{ - Priv: ident.PrivateKey, - Pub: signer.PublicKey().Marshal(), - Cert: ident.Certs.SSH, - TLSCert: ident.Certs.TLS, - TrustedCA: trustedCA, - }, hostAuthFunc, nil -} - // authFromIdentity returns a standard ssh.Authmethod for a given identity file func authFromIdentity(k *client.Key) (ssh.AuthMethod, error) { signer, err := sshutils.NewSigner(k.Priv, k.Cert) @@ -1227,7 +1039,7 @@ func authFromIdentity(k *client.Key) (ssh.AuthMethod, error) { // onShow reads an identity file (a public SSH key or a cert) and dumps it to stdout func onShow(cf *CLIConf) { - key, _, err := loadIdentity(cf.IdentityFileIn) + key, _, err := common.LoadIdentity(cf.IdentityFileIn) // unmarshal certificate bytes into a ssh.PublicKey cert, _, _, _, err := ssh.ParseAuthorizedKey(key.Cert) diff --git a/tool/tsh/tsh_test.go b/tool/tsh/tsh_test.go index 0caec03e6c839..7ff1019befd7f 100644 --- a/tool/tsh/tsh_test.go +++ b/tool/tsh/tsh_test.go @@ -29,6 +29,7 @@ import ( "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/tool/tsh/common" "gopkg.in/check.v1" ) @@ -104,7 +105,7 @@ func (s *MainTestSuite) TestIdentityRead(c *check.C) { } for _, id := range ids { // test reading: - k, cb, err := loadIdentity(fmt.Sprintf("../../fixtures/certs/identities/%s", id)) + k, cb, err := common.LoadIdentity(fmt.Sprintf("../../fixtures/certs/identities/%s", id)) c.Assert(err, check.IsNil) c.Assert(k, check.NotNil) c.Assert(cb, check.IsNil) @@ -114,12 +115,12 @@ func (s *MainTestSuite) TestIdentityRead(c *check.C) { c.Assert(err, check.IsNil) c.Assert(am, check.NotNil) } - k, _, err := loadIdentity("../../fixtures/certs/identities/lonekey") + k, _, err := common.LoadIdentity("../../fixtures/certs/identities/lonekey") c.Assert(k, check.IsNil) c.Assert(err, check.NotNil) // lets read an indentity which includes a CA cert - k, hostAuthCallback, err := loadIdentity("../../fixtures/certs/identities/key-cert-ca.pem") + k, hostAuthCallback, err := common.LoadIdentity("../../fixtures/certs/identities/key-cert-ca.pem") c.Assert(err, check.IsNil) c.Assert(k, check.NotNil) c.Assert(hostAuthCallback, check.NotNil) @@ -134,7 +135,7 @@ func (s *MainTestSuite) TestIdentityRead(c *check.C) { c.Assert(err, check.IsNil) // load an identity which include TLS certificates - k, _, err = loadIdentity("../../fixtures/certs/identities/tls.pem") + k, _, err = common.LoadIdentity("../../fixtures/certs/identities/tls.pem") c.Assert(err, check.IsNil) c.Assert(k, check.NotNil) c.Assert(k.TLSCert, check.NotNil)