Files
teleport/lib/devicetrust/authz/authz_test.go
T
Alan Parra a469c4ce2b feat!: Apply role device mode to App Access (#35274)
* Apply role device mode to App Access

* nit: Don't capture traces on guard errors

* Consistently return ErrTrustedDeviceRequired

* Surface device error trust error for apps

* Reply with a custom message for device trust errors

* Review: Remove the DisableDeviceAuthorization field

* Review: Refactor device trust message into its own method

* Review: Wrap guard errors

* Review: Simplify device error message
2023-12-04 17:53:30 +00:00

241 lines
6.6 KiB
Go

/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package authz_test
import (
"testing"
"github.com/gravitational/trace"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/devicetrust/authz"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/tlsca"
)
func TestIsTLSDeviceVerified(t *testing.T) {
testIsDeviceVerified(t, "IsTLSDeviceVerified", authz.IsTLSDeviceVerified)
}
func TestIsSSHDeviceVerified(t *testing.T) {
testIsDeviceVerified(t, "IsSSHDeviceVerified", func(ext *tlsca.DeviceExtensions) bool {
var cert *ssh.Certificate
if ext != nil {
cert = &ssh.Certificate{
Permissions: ssh.Permissions{
Extensions: map[string]string{
teleport.CertExtensionDeviceID: ext.DeviceID,
teleport.CertExtensionDeviceAssetTag: ext.AssetTag,
teleport.CertExtensionDeviceCredentialID: ext.CredentialID,
},
},
}
}
return authz.IsSSHDeviceVerified(cert)
})
}
func testIsDeviceVerified(t *testing.T, name string, fn func(ext *tlsca.DeviceExtensions) bool) {
tests := []struct {
name string
ext *tlsca.DeviceExtensions
want bool
}{
{
name: "all extensions",
ext: &tlsca.DeviceExtensions{
DeviceID: "deviceid1",
AssetTag: "assettag1",
CredentialID: "credentialid1",
},
want: true,
},
{
name: "nok: nil extensions",
},
{
name: "nok: empty extensions",
ext: &tlsca.DeviceExtensions{},
},
{
name: "nok: missing DeviceID",
ext: &tlsca.DeviceExtensions{
AssetTag: "assettag1",
CredentialID: "credentialid1",
},
},
{
name: "nok: missing AssetTag",
ext: &tlsca.DeviceExtensions{
DeviceID: "deviceid1",
CredentialID: "credentialid1",
},
},
{
name: "nok: missing CredentialID",
ext: &tlsca.DeviceExtensions{
DeviceID: "deviceid1",
AssetTag: "assettag1",
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
got := fn(test.ext)
if got != test.want {
t.Errorf("%v(%#v) = %v, want = %v", name, test.ext, got, test.want)
}
})
}
}
func TestVerifyTLSUser(t *testing.T) {
runVerifyUserTest(t, "VerifyTLSUser", func(dt *types.DeviceTrust, ext *tlsca.DeviceExtensions) error {
return authz.VerifyTLSUser(dt, tlsca.Identity{
Username: "llama",
DeviceExtensions: *ext,
})
})
}
func TestVerifySSHUser(t *testing.T) {
runVerifyUserTest(t, "VerifySSHUser", func(dt *types.DeviceTrust, ext *tlsca.DeviceExtensions) error {
return authz.VerifySSHUser(dt, &ssh.Certificate{
KeyId: "llama",
Permissions: ssh.Permissions{
Extensions: map[string]string{
teleport.CertExtensionDeviceID: ext.DeviceID,
teleport.CertExtensionDeviceAssetTag: ext.AssetTag,
teleport.CertExtensionDeviceCredentialID: ext.CredentialID,
},
},
})
})
}
func runVerifyUserTest(t *testing.T, method string, verify func(dt *types.DeviceTrust, ext *tlsca.DeviceExtensions) error) {
assertNoErr := func(t *testing.T, err error) {
assert.NoError(t, err, "%v mismatch", method)
}
assertDeniedErr := func(t *testing.T, err error) {
assert.ErrorContains(t, err, "trusted device", "%v mismatch", method)
assert.True(t, trace.IsAccessDenied(err), "%v returned an error other than trace.AccessDeniedError: %T", method, err)
}
userWithoutExtensions := &tlsca.DeviceExtensions{}
userWithExtensions := &tlsca.DeviceExtensions{
DeviceID: "deviceid1",
AssetTag: "assettag1",
CredentialID: "credentialid1",
}
tests := []struct {
name string
buildType string
dt *types.DeviceTrust
ext *tlsca.DeviceExtensions
assertErr func(t *testing.T, err error)
}{
{
name: "OSS dt=nil",
buildType: modules.BuildOSS,
dt: nil, // OK, config absent.
ext: userWithoutExtensions,
assertErr: assertNoErr,
},
{
name: "OSS mode=off",
buildType: modules.BuildOSS,
dt: &types.DeviceTrust{
Mode: constants.DeviceTrustModeOff, // Valid for OSS.
},
ext: userWithoutExtensions,
assertErr: assertNoErr,
},
{
name: "OSS mode never enforced",
buildType: modules.BuildOSS,
dt: &types.DeviceTrust{
Mode: constants.DeviceTrustModeRequired, // Invalid for OSS, treated as "off".
},
ext: userWithoutExtensions,
assertErr: assertNoErr,
},
{
name: "Enterprise mode=off",
buildType: modules.BuildEnterprise,
dt: &types.DeviceTrust{
Mode: constants.DeviceTrustModeOff,
},
ext: userWithoutExtensions,
assertErr: assertNoErr,
},
{
name: "Enterprise mode=optional without extensions",
buildType: modules.BuildEnterprise,
dt: &types.DeviceTrust{
Mode: constants.DeviceTrustModeOptional,
},
ext: userWithoutExtensions,
assertErr: assertNoErr,
},
{
name: "Enterprise mode=optional with extensions",
buildType: modules.BuildEnterprise,
dt: &types.DeviceTrust{
Mode: constants.DeviceTrustModeOptional,
},
ext: userWithExtensions, // Happens if the device is enrolled.
assertErr: assertNoErr,
},
{
name: "nok: Enterprise mode=required without extensions",
buildType: modules.BuildEnterprise,
dt: &types.DeviceTrust{
Mode: constants.DeviceTrustModeRequired,
},
ext: userWithoutExtensions,
assertErr: assertDeniedErr,
},
{
name: "Enterprise mode=required with extensions",
buildType: modules.BuildEnterprise,
dt: &types.DeviceTrust{
Mode: constants.DeviceTrustModeRequired,
},
ext: userWithExtensions,
assertErr: assertNoErr,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
modules.SetTestModules(t, &modules.TestModules{
TestBuildType: test.buildType,
})
test.assertErr(t, verify(test.dt, test.ext))
})
}
}