api dependency reduction - ssh (#5379)

Move Cert Authority methods out of api to remove dependency on crypto/ssh.
This commit is contained in:
Brian Joerger 2021-01-29 10:28:24 -08:00 committed by GitHub
parent 74f7c801da
commit efe91c4def
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 162 additions and 159 deletions

View file

@ -21,8 +21,6 @@ import (
"fmt"
"time"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/utils"
@ -70,10 +68,6 @@ type CertAuthority interface {
SetCheckingKeys([][]byte) error
// AddRole adds a role to ca role list
AddRole(name string)
// Checkers returns public keys that can be used to check cert authorities
Checkers() ([]ssh.PublicKey, error)
// Signers returns a list of signers that could be used to sign keys
Signers() ([]ssh.Signer, error)
// String returns human readable version of the CertAuthority
String() string
// SetTLSKeyPairs sets TLS key pairs
@ -89,9 +83,9 @@ type CertAuthority interface {
// SetRotation sets rotation state.
SetRotation(Rotation)
// GetSigningAlg returns the signing algorithm used by signing keys.
GetSigningAlg() string
GetSigningAlg() CertAuthoritySpecV2_SigningAlgType
// SetSigningAlg sets the signing algorithm used by signing keys.
SetSigningAlg(string)
SetSigningAlg(CertAuthoritySpecV2_SigningAlgType)
// Clone returns a copy of the cert authority object.
Clone() CertAuthority
}
@ -348,71 +342,14 @@ func (ca *CertAuthorityV2) ID() *CertAuthID {
return &CertAuthID{DomainName: ca.Spec.ClusterName, Type: ca.Spec.Type}
}
// Checkers returns public keys that can be used to check cert authorities
func (ca *CertAuthorityV2) Checkers() ([]ssh.PublicKey, error) {
out := make([]ssh.PublicKey, 0, len(ca.Spec.CheckingKeys))
for _, keyBytes := range ca.Spec.CheckingKeys {
key, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
if err != nil {
return nil, trace.BadParameter("invalid authority public key (len=%d): %v", len(keyBytes), err)
}
out = append(out, key)
}
return out, nil
}
// Signers returns a list of signers that could be used to sign keys.
func (ca *CertAuthorityV2) Signers() ([]ssh.Signer, error) {
out := make([]ssh.Signer, 0, len(ca.Spec.SigningKeys))
for _, keyBytes := range ca.Spec.SigningKeys {
signer, err := ssh.ParsePrivateKey(keyBytes)
if err != nil {
return nil, trace.Wrap(err)
}
signer = utils.AlgSigner(signer, ca.GetSigningAlg())
out = append(out, signer)
}
return out, nil
}
// GetSigningAlg returns the CA's signing algorithm type
func (ca *CertAuthorityV2) GetSigningAlg() string {
switch ca.Spec.SigningAlg {
// UNKNOWN algorithm can come from a cluster that existed before SigningAlg
// field was added. Default to RSA-SHA1 to match the implicit algorithm
// used in those clusters.
case CertAuthoritySpecV2_RSA_SHA1, CertAuthoritySpecV2_UNKNOWN:
return ssh.SigAlgoRSA
case CertAuthoritySpecV2_RSA_SHA2_256:
return ssh.SigAlgoRSASHA2256
case CertAuthoritySpecV2_RSA_SHA2_512:
return ssh.SigAlgoRSASHA2512
default:
return ""
}
}
// ParseSigningAlg converts the SSH signature algorithm strings to the
// corresponding proto enum value.
//
// alg should be one of ssh.SigAlgo* If it's not one of those
// constants, CertAuthoritySpecV2_UNKNOWN is returned.
func ParseSigningAlg(alg string) CertAuthoritySpecV2_SigningAlgType {
switch alg {
case ssh.SigAlgoRSA:
return CertAuthoritySpecV2_RSA_SHA1
case ssh.SigAlgoRSASHA2256:
return CertAuthoritySpecV2_RSA_SHA2_256
case ssh.SigAlgoRSASHA2512:
return CertAuthoritySpecV2_RSA_SHA2_512
default:
return CertAuthoritySpecV2_UNKNOWN
}
func (ca *CertAuthorityV2) GetSigningAlg() CertAuthoritySpecV2_SigningAlgType {
return ca.Spec.SigningAlg
}
// SetSigningAlg sets the CA's signing algorith type
func (ca *CertAuthorityV2) SetSigningAlg(alg string) {
ca.Spec.SigningAlg = ParseSigningAlg(alg)
func (ca *CertAuthorityV2) SetSigningAlg(alg CertAuthoritySpecV2_SigningAlgType) {
ca.Spec.SigningAlg = alg
}
// CheckAndSetDefaults checks and set default values for any missing fields.

View file

@ -1,68 +0,0 @@
/*
Copyright 2021 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 utils
import (
"io"
"golang.org/x/crypto/ssh"
)
// AlgSigner wraps a provided ssh.Signer to ensure signature algorithm
// compatibility with OpenSSH.
//
// Right now it allows forcing SHA-2 signatures with RSA keys, instead of the
// default SHA-1 used by x/crypto/ssh. See
// https://www.openssh.com/txt/release-8.2 for context.
//
// If the provided Signer is not an RSA key or does not implement
// ssh.AlgorithmSigner, it's returned as is.
//
// DELETE IN 5.0: assuming https://github.com/golang/go/issues/37278 is fixed
// by then and we pull in the fix. Also delete all call sites.
func AlgSigner(s ssh.Signer, alg string) ssh.Signer {
if alg == "" {
return s
}
if s.PublicKey().Type() != ssh.KeyAlgoRSA && s.PublicKey().Type() != ssh.CertAlgoRSAv01 {
return s
}
as, ok := s.(ssh.AlgorithmSigner)
if !ok {
return s
}
return fixedAlgorithmSigner{
AlgorithmSigner: as,
alg: alg,
}
}
type fixedAlgorithmSigner struct {
ssh.AlgorithmSigner
alg string
}
func (s fixedAlgorithmSigner) SignWithAlgorithm(rand io.Reader, data []byte, alg string) (*ssh.Signature, error) {
if alg == "" {
alg = s.alg
}
return s.AlgorithmSigner.SignWithAlgorithm(rand, data, alg)
}
func (s fixedAlgorithmSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
return s.AlgorithmSigner.SignWithAlgorithm(rand, data, s.alg)
}

View file

@ -57,6 +57,7 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/testlog"
@ -3898,11 +3899,11 @@ func (s *IntSuite) TestRotateChangeSigningAlg(c *check.C) {
assertSigningAlg := func(svc *service.TeleportProcess, alg string) {
hostCA, err := svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.HostCA, DomainName: Site}, false)
c.Assert(err, check.IsNil)
c.Assert(hostCA.GetSigningAlg(), check.Equals, alg)
c.Assert(sshutils.GetSigningAlgName(hostCA), check.Equals, alg)
userCA, err := svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.UserCA, DomainName: Site}, false)
c.Assert(err, check.IsNil)
c.Assert(userCA.GetSigningAlg(), check.Equals, alg)
c.Assert(sshutils.GetSigningAlgName(userCA), check.Equals, alg)
}
rotate := func(svc *service.TeleportProcess, mode string) *service.TeleportProcess {

View file

@ -394,7 +394,7 @@ func (a *Server) GenerateHostCert(hostPublicKey []byte, hostID, nodeName string,
// create and sign!
return a.Authority.GenerateHostCert(services.HostCertParams{
PrivateCASigningKey: caPrivateKey,
CASigningAlg: ca.GetSigningAlg(),
CASigningAlg: sshutils.GetSigningAlgName(ca),
PublicHostKey: hostPublicKey,
HostID: hostID,
NodeName: nodeName,
@ -634,7 +634,7 @@ func (a *Server) generateUserCert(req certRequest) (*certs, error) {
}
sshCert, err := a.Authority.GenerateUserCert(services.UserCertParams{
PrivateCASigningKey: privateKey,
CASigningAlg: ca.GetSigningAlg(),
CASigningAlg: sshutils.GetSigningAlgName(ca),
PublicUserKey: req.publicKey,
Username: req.user.GetName(),
AllowedLogins: allowedLogins,
@ -1238,7 +1238,7 @@ func (a *Server) GenerateServerKeys(req GenerateServerKeysRequest) (*PackedKeys,
// generate hostSSH certificate
hostSSHCert, err := a.Authority.GenerateHostCert(services.HostCertParams{
PrivateCASigningKey: caPrivateKey,
CASigningAlg: ca.GetSigningAlg(),
CASigningAlg: sshutils.GetSigningAlgName(ca),
PublicHostKey: pubSSHKey,
HostID: req.HostID,
NodeName: req.NodeName,

View file

@ -343,7 +343,7 @@ func Init(cfg InitConfig, opts ...ServerOption) (*Server, error) {
ClusterName: cfg.ClusterName.GetClusterName(),
Type: services.UserCA,
SigningKeys: [][]byte{priv},
SigningAlg: services.ParseSigningAlg(sigAlg),
SigningAlg: sshutils.ParseSigningAlg(sigAlg),
CheckingKeys: [][]byte{pub},
TLSKeyPairs: []services.TLSKeyPair{{Cert: certPEM, Key: keyPEM}},
},
@ -403,7 +403,7 @@ func Init(cfg InitConfig, opts ...ServerOption) (*Server, error) {
ClusterName: cfg.ClusterName.GetClusterName(),
Type: services.HostCA,
SigningKeys: [][]byte{priv},
SigningAlg: services.ParseSigningAlg(sigAlg),
SigningAlg: sshutils.ParseSigningAlg(sigAlg),
CheckingKeys: [][]byte{pub},
TLSKeyPairs: []services.TLSKeyPair{{Cert: certPEM, Key: keyPEM}},
},

View file

@ -31,6 +31,7 @@ import (
"github.com/gravitational/teleport/lib/backend/lite"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
@ -351,12 +352,12 @@ func (s *AuthInitSuite) TestCASigningAlg(c *C) {
hostCAs, err := auth.GetCertAuthorities(services.HostCA, false)
c.Assert(err, IsNil)
for _, ca := range hostCAs {
c.Assert(ca.GetSigningAlg(), Equals, alg)
c.Assert(sshutils.GetSigningAlgName(ca), Equals, alg)
}
userCAs, err := auth.GetCertAuthorities(services.UserCA, false)
c.Assert(err, IsNil)
for _, ca := range userCAs {
c.Assert(ca.GetSigningAlg(), Equals, alg)
c.Assert(sshutils.GetSigningAlgName(ca), Equals, alg)
}
}

View file

@ -25,6 +25,7 @@ import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/jwt"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
@ -611,7 +612,7 @@ func startNewRotation(req rotationReq, ca services.CertAuthority) error {
// explicitly set in the config file. If the config file doesn't set a value,
// preserve the signing algorithm of the existing CA.
if req.caSigningAlg != nil {
ca.SetSigningAlg(*req.caSigningAlg)
sshutils.SetSigningAlgName(ca, *req.caSigningAlg)
}
return nil

View file

@ -234,7 +234,7 @@ func (a *Agent) checkHostSignature(hostport string, remote net.Addr, key ssh.Pub
return trace.Wrap(err, "failed to fetch remote certs")
}
for _, ca := range cas {
checkers, err := ca.Checkers()
checkers, err := sshutils.GetCheckers(ca)
if err != nil {
return trace.BadParameter("error parsing key: %v", err)
}

View file

@ -704,7 +704,7 @@ func (s *server) getTrustedCAKeysByID(id services.CertAuthID) ([]ssh.PublicKey,
if err != nil {
return nil, trace.Wrap(err)
}
return ca.Checkers()
return sshutils.GetCheckers(ca)
}
func (s *server) keyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (perm *ssh.Permissions, err error) {

View file

@ -26,11 +26,12 @@ import (
"github.com/gravitational/teleport/api/types/wrappers"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/jwt"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/jonboulle/clockwork"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
"github.com/tstranex/u2f"
)
@ -93,10 +94,10 @@ func checkUserOrHostCA(ca CertAuthority) error {
if len(ca.GetTLSKeyPairs()) == 0 {
return trace.BadParameter("certificate authority missing TLS key pairs")
}
if _, err := ca.Checkers(); err != nil {
if _, err := sshutils.GetCheckers(ca); err != nil {
return trace.Wrap(err)
}
if _, err := ca.Signers(); err != nil {
if _, err := sshutils.GetSigners(ca); err != nil {
return trace.Wrap(err)
}
// This is to force users to migrate

View file

@ -172,7 +172,6 @@ var (
RemoveCASecrets = types.RemoveCASecrets
MarshalCertRoles = types.MarshalCertRoles
UnmarshalCertRoles = types.UnmarshalCertRoles
ParseSigningAlg = types.ParseSigningAlg
RotationSchema = types.RotationSchema
)

View file

@ -409,7 +409,7 @@ func (h *AuthHandlers) authorityForCert(caType services.CertAuthType, key ssh.Pu
// find the one that signed our certificate
var ca services.CertAuthority
for i := range cas {
checkers, err := cas[i].Checkers()
checkers, err := sshutils.GetCheckers(cas[i])
if err != nil {
h.Warnf("%v", err)
return nil, trace.Wrap(err)

91
lib/sshutils/authority.go Normal file
View file

@ -0,0 +1,91 @@
/*
Copyright 2021 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 sshutils
import (
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
)
// GetCheckers returns public keys that can be used to check cert authorities
func GetCheckers(ca types.CertAuthority) ([]ssh.PublicKey, error) {
out := make([]ssh.PublicKey, 0, len(ca.GetCheckingKeys()))
for _, keyBytes := range ca.GetCheckingKeys() {
key, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
if err != nil {
return nil, trace.BadParameter("invalid authority public key (len=%d): %v", len(keyBytes), err)
}
out = append(out, key)
}
return out, nil
}
// GetSigners returns a list of signers that could be used to sign keys.
func GetSigners(ca types.CertAuthority) ([]ssh.Signer, error) {
out := make([]ssh.Signer, 0, len(ca.GetSigningKeys()))
for _, keyBytes := range ca.GetSigningKeys() {
signer, err := ssh.ParsePrivateKey(keyBytes)
if err != nil {
return nil, trace.Wrap(err)
}
signer = AlgSigner(signer, GetSigningAlgName(ca))
out = append(out, signer)
}
return out, nil
}
// GetSigningAlgName returns the CA's signing algorithm type
func GetSigningAlgName(ca types.CertAuthority) string {
switch ca.GetSigningAlg() {
// UNKNOWN algorithm can come from a cluster that existed before SigningAlg
// field was added. Default to RSA-SHA1 to match the implicit algorithm
// used in those clusters.
case types.CertAuthoritySpecV2_RSA_SHA1, types.CertAuthoritySpecV2_UNKNOWN:
return ssh.SigAlgoRSA
case types.CertAuthoritySpecV2_RSA_SHA2_256:
return ssh.SigAlgoRSASHA2256
case types.CertAuthoritySpecV2_RSA_SHA2_512:
return ssh.SigAlgoRSASHA2512
default:
return ""
}
}
// SetSigningAlgName sets the signing algorithm type for the given CA
func SetSigningAlgName(ca types.CertAuthority, alg string) {
ca.SetSigningAlg(ParseSigningAlg(alg))
}
// ParseSigningAlg converts the name of the SSH signature algorithm to the
// corresponding proto enum value.
//
// alg should be one of ssh.SigAlgo* constants. If it's not one of those
// constants, types.CertAuthoritySpecV2_UNKNOWN is returned.
func ParseSigningAlg(alg string) types.CertAuthoritySpecV2_SigningAlgType {
switch alg {
case ssh.SigAlgoRSA:
return types.CertAuthoritySpecV2_RSA_SHA1
case ssh.SigAlgoRSASHA2256:
return types.CertAuthoritySpecV2_RSA_SHA2_256
case ssh.SigAlgoRSASHA2512:
return types.CertAuthoritySpecV2_RSA_SHA2_512
default:
return types.CertAuthoritySpecV2_UNKNOWN
}
}

View file

@ -18,17 +18,57 @@ package sshutils
import (
"crypto"
"io"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/trace"
)
// AlgSigner has been moved to /api/utils, and is now
// imported here for backwards compatibility. DELETE IN 7.0.0
var AlgSigner = utils.AlgSigner
// AlgSigner wraps the provided ssh.Signer to ensure signature algorithm
// compatibility with OpenSSH.
//
// Right now it allows forcing SHA-2 signatures with RSA keys, instead of the
// default SHA-1 used by x/crypto/ssh. See
// https://www.openssh.com/txt/release-8.2 for context.
//
// If the provided Signer is not an RSA key or does not implement
// ssh.AlgorithmSigner, it's returned as is.
//
// DELETE IN 5.0: assuming https://github.com/golang/go/issues/37278 is fixed
// by then and we pull in the fix. Also delete all call sites.
func AlgSigner(s ssh.Signer, alg string) ssh.Signer {
if alg == "" {
return s
}
if s.PublicKey().Type() != ssh.KeyAlgoRSA && s.PublicKey().Type() != ssh.CertAlgoRSAv01 {
return s
}
as, ok := s.(ssh.AlgorithmSigner)
if !ok {
return s
}
return fixedAlgorithmSigner{
AlgorithmSigner: as,
alg: alg,
}
}
type fixedAlgorithmSigner struct {
ssh.AlgorithmSigner
alg string
}
func (s fixedAlgorithmSigner) SignWithAlgorithm(rand io.Reader, data []byte, alg string) (*ssh.Signature, error) {
if alg == "" {
alg = s.alg
}
return s.AlgorithmSigner.SignWithAlgorithm(rand, data, alg)
}
func (s fixedAlgorithmSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
return s.AlgorithmSigner.SignWithAlgorithm(rand, data, s.alg)
}
// NewSigner returns new ssh Signer from private key + certificate pair. The
// signer can be used to create "auth methods" i.e. login into Teleport SSH