Files
teleport/lib/reversetunnel/srv_test.go
T
rosstimothy 550d23d15d Fix goroutine and memory leak in watchCertAuthorities (#10871)
* Fix goroutine and memory leak in watchCertAuthorities

The CA Watcher was blocking both on writing to a channel when the watcher
was closed and on HTTP calls that had no request timeout or context passed
to cause cancellation.

All resourceWatcher implementations that had a bug which may cause them to block
on writing to a channel forever were fixed by selecting on the write and ctx.Done.

Adding context.Context to all Get/Put/Post/Delete methods on the auth HTTPClient to
force callers to propagate context. Prior all calls used context.TODO which
prevents requests from being properly cancelled.

Add context propagation to RotateCertAuthority, RotateExternalCertAuthority,
GetCertAuthority, GetCertAuthorities. This is needed to get the correct ctx
from the CertAtuhorityWatcher all the way down to the HTTPClient that makes
the call.

Closes #10648
2022-03-10 11:05:39 -05:00

162 lines
4.5 KiB
Go

/*
Copyright 2020 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 reversetunnel
import (
"context"
"net"
"testing"
"time"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
)
func TestServerKeyAuth(t *testing.T) {
ta := testauthority.New()
priv, pub, err := ta.GenerateKeyPair("")
require.NoError(t, err)
caSigner, err := ssh.ParsePrivateKey(priv)
require.NoError(t, err)
ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{
Type: types.HostCA,
ClusterName: "cluster-name",
ActiveKeys: types.CAKeySet{
SSH: []*types.SSHKeyPair{{
PrivateKey: priv,
PrivateKeyType: types.PrivateKeyType_RAW,
PublicKey: pub,
}},
},
Roles: nil,
SigningAlg: types.CertAuthoritySpecV2_RSA_SHA2_256,
})
require.NoError(t, err)
s := &server{
log: utils.NewLoggerForTests(),
localAccessPoint: mockAccessPoint{
ca: ca,
},
}
con := mockSSHConnMetadata{}
tests := []struct {
desc string
key ssh.PublicKey
wantExtensions map[string]string
wantErr require.ErrorAssertionFunc
}{
{
desc: "host cert",
key: func() ssh.PublicKey {
rawCert, err := ta.GenerateHostCert(services.HostCertParams{
CASigner: caSigner,
CASigningAlg: defaults.CASignatureAlgorithm,
PublicHostKey: pub,
HostID: "host-id",
NodeName: con.User(),
ClusterName: "host-cluster-name",
Role: types.RoleNode,
})
require.NoError(t, err)
key, _, _, _, err := ssh.ParseAuthorizedKey(rawCert)
require.NoError(t, err)
return key
}(),
wantExtensions: map[string]string{
extHost: con.User(),
utils.ExtIntCertType: utils.ExtIntCertTypeHost,
extCertRole: string(types.RoleNode),
extAuthority: "host-cluster-name",
},
wantErr: require.NoError,
},
{
desc: "user cert",
key: func() ssh.PublicKey {
rawCert, err := ta.GenerateUserCert(services.UserCertParams{
CASigner: caSigner,
CASigningAlg: defaults.CASignatureAlgorithm,
PublicUserKey: pub,
Username: con.User(),
AllowedLogins: []string{con.User()},
Roles: []string{"dev", "admin"},
RouteToCluster: "user-cluster-name",
CertificateFormat: constants.CertificateFormatStandard,
TTL: time.Minute,
})
require.NoError(t, err)
key, _, _, _, err := ssh.ParseAuthorizedKey(rawCert)
require.NoError(t, err)
return key
}(),
wantExtensions: map[string]string{
extHost: con.User(),
utils.ExtIntCertType: utils.ExtIntCertTypeUser,
extCertRole: "dev",
extAuthority: "user-cluster-name",
},
wantErr: require.NoError,
},
{
desc: "not a cert",
key: func() ssh.PublicKey {
key, _, _, _, err := ssh.ParseAuthorizedKey(pub)
require.NoError(t, err)
return key
}(),
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
perm, err := s.keyAuth(con, tt.key)
tt.wantErr(t, err)
if err == nil {
require.Empty(t, cmp.Diff(perm, &ssh.Permissions{Extensions: tt.wantExtensions}))
}
})
}
}
type mockSSHConnMetadata struct {
ssh.ConnMetadata
}
func (mockSSHConnMetadata) User() string { return "conn-user" }
func (mockSSHConnMetadata) RemoteAddr() net.Addr { return &net.TCPAddr{} }
type mockAccessPoint struct {
auth.ProxyAccessPoint
ca types.CertAuthority
}
func (ap mockAccessPoint) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) {
return ap.ca, nil
}