github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/device_test.go (about)

     1  // Copyright 2023 Gravitational, Inc
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package types
    16  
    17  import (
    18  	"crypto"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  	"google.golang.org/protobuf/testing/protocmp"
    26  	"google.golang.org/protobuf/types/known/timestamppb"
    27  
    28  	devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
    29  )
    30  
    31  func TestDeviceConversions_toAndFrom(t *testing.T) {
    32  	t1 := time.UnixMilli(1680276526972000) // Fri Mar 31 2023 15:28:46 UTC
    33  	t11 := t1.Add(100 * time.Millisecond)
    34  	t2 := t1.Add(1 * time.Second)
    35  	t22 := t1.Add(100 * time.Millisecond)
    36  
    37  	const osType = devicepb.OSType_OS_TYPE_MACOS
    38  	const assetTag = "llama14"
    39  	dev := &devicepb.Device{
    40  		ApiVersion:   "v1",
    41  		Id:           "0af7c335-5f2c-4756-8266-9965a47ccbd3",
    42  		OsType:       osType,
    43  		AssetTag:     assetTag,
    44  		CreateTime:   timestamppb.New(t1),
    45  		UpdateTime:   timestamppb.New(t2),
    46  		EnrollStatus: devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED,
    47  		Credential: &devicepb.DeviceCredential{
    48  			Id:                    "557762f0-4cd4-4b75-aaee-575c57237c0b",
    49  			PublicKeyDer:          []byte("insert public key here"),
    50  			DeviceAttestationType: devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_UNSPECIFIED,
    51  			TpmEkcertSerial:       "00:00:00:00:00:00:00:00:00:00:00:DE:AD:BE:EF:CA:FE",
    52  			TpmAkPublic:           []byte("a TPMT_PUBLIC encoded blob"),
    53  		},
    54  		CollectedData: []*devicepb.DeviceCollectedData{
    55  			{
    56  				CollectTime:  timestamppb.New(t1),
    57  				RecordTime:   timestamppb.New(t11),
    58  				OsType:       osType,
    59  				SerialNumber: assetTag,
    60  			},
    61  			{
    62  				CollectTime:             timestamppb.New(t2),
    63  				RecordTime:              timestamppb.New(t22),
    64  				OsType:                  osType,
    65  				SerialNumber:            assetTag,
    66  				ModelIdentifier:         "MacBookPro9,2",
    67  				OsVersion:               "13.1.2",
    68  				OsBuild:                 "22D68",
    69  				OsUsername:              "llama",
    70  				JamfBinaryVersion:       "9.27",
    71  				MacosEnrollmentProfiles: "Enrolled via DEP: No\nMDM enrollment: Yes (User Approved)\nMDM server: ...",
    72  				ReportedAssetTag:        assetTag + "-reported",
    73  				SystemSerialNumber:      assetTag + "-system",
    74  				BaseBoardSerialNumber:   assetTag + "-board",
    75  				TpmPlatformAttestation: &devicepb.TPMPlatformAttestation{
    76  					Nonce: []byte("foo-bar-bizz"),
    77  					PlatformParameters: &devicepb.TPMPlatformParameters{
    78  						EventLog: []byte("dummy-event-log"),
    79  						Quotes: []*devicepb.TPMQuote{
    80  							{
    81  								Quote:     []byte("fake-quote-1"),
    82  								Signature: []byte("fake-signature-1"),
    83  							},
    84  							{
    85  								Quote:     []byte("fake-quote-2"),
    86  								Signature: []byte("fake-signature-2"),
    87  							},
    88  						},
    89  						Pcrs: []*devicepb.TPMPCR{
    90  							{
    91  								Index:     0,
    92  								Digest:    []byte("fake-sha1-digest"),
    93  								DigestAlg: uint64(crypto.SHA1),
    94  							},
    95  							{
    96  								Index:     1,
    97  								Digest:    []byte("fake-sha256-digest"),
    98  								DigestAlg: uint64(crypto.SHA256),
    99  							},
   100  						},
   101  					},
   102  				},
   103  				OsId: "macOS", // Made up, only set for Linux.
   104  			},
   105  		},
   106  		Source: &devicepb.DeviceSource{
   107  			Name:   "myscript",
   108  			Origin: devicepb.DeviceOrigin_DEVICE_ORIGIN_API,
   109  		},
   110  		Profile: &devicepb.DeviceProfile{
   111  			UpdateTime:          timestamppb.New(t1),
   112  			ModelIdentifier:     "MacBookPro9,2",
   113  			OsVersion:           "13.1.2",
   114  			OsBuild:             "22F82",
   115  			OsBuildSupplemental: "22F770820d",
   116  			OsUsernames:         []string{"admin", "llama"},
   117  			JamfBinaryVersion:   "9.27",
   118  			ExternalId:          "99",
   119  			OsId:                "macOS", // Made up, only set for Linux.
   120  		},
   121  		Owner: "llama",
   122  	}
   123  
   124  	gotRes := DeviceToResource(dev)
   125  	// Assert some of the more "unusual" or missing fields.
   126  	// We know other information isn't lost because of the conversion below,
   127  	// therefore it must be present in the resource.
   128  	assert.Equal(t, dev.ApiVersion, gotRes.Version, "resource.Version is not the ApiVersion")
   129  	assert.Equal(t, dev.Id, gotRes.Metadata.Name, "resource.Metadata.Name is not the Id")
   130  	assert.NotEmpty(t, gotRes.Metadata.Namespace, "resource.Metadata.Namespace")
   131  
   132  	gotDev, err := DeviceFromResource(gotRes)
   133  	require.NoError(t, err, "DeviceFromResource failed")
   134  	if diff := cmp.Diff(dev, gotDev, protocmp.Transform()); diff != "" {
   135  		t.Errorf("DeviceFromResource mismatch (-want +got)\n%s", diff)
   136  	}
   137  }
   138  
   139  func TestResourceAttestationType_toAndFrom(t *testing.T) {
   140  	t.Parallel()
   141  	tests := []struct {
   142  		attestationType string
   143  		wantEmpty       bool
   144  		wantErr         string
   145  	}{
   146  		{
   147  			attestationType: "unspecified",
   148  			wantEmpty:       true,
   149  		},
   150  		{
   151  			attestationType: "tpm_ekpub",
   152  		},
   153  		{
   154  			attestationType: "tpm_ekcert",
   155  		},
   156  		{
   157  			attestationType: "tpm_ekcert_trusted",
   158  		},
   159  		{
   160  			attestationType: "quantum_entanglement",
   161  			wantErr:         "unknown attestation type",
   162  		},
   163  	}
   164  	for _, tt := range tests {
   165  		t.Run(tt.attestationType, func(t *testing.T) {
   166  			asEnum, err := ResourceDeviceAttestationTypeFromString(tt.attestationType)
   167  			if tt.wantErr != "" {
   168  				require.ErrorContains(t, err, tt.wantErr, "ResourceDeviceAttestationTypeFromString error mismatch")
   169  				return
   170  			}
   171  
   172  			got := ResourceDeviceAttestationTypeToString(asEnum)
   173  			want := tt.attestationType
   174  			if tt.wantEmpty {
   175  				want = ""
   176  			}
   177  			require.Equal(t, want, got, "ResourceDeviceAttestationTypeToString mismatch")
   178  		})
   179  	}
   180  }
   181  
   182  func TestAllDeviceEnumsMapped(t *testing.T) {
   183  	tests := []struct {
   184  		name       string
   185  		nameMap    map[int32]string // a proto enum "name" map, like MyEnum_name.
   186  		toString   func(i int32) string
   187  		fromString func(s string) (int32, error)
   188  	}{
   189  		{
   190  			name:    "OSType",
   191  			nameMap: devicepb.OSType_name,
   192  			toString: func(i int32) string {
   193  				return ResourceOSTypeToString(devicepb.OSType(i))
   194  			},
   195  			fromString: func(s string) (int32, error) {
   196  				val, err := ResourceOSTypeFromString(s)
   197  				return int32(val), err
   198  			},
   199  		},
   200  		{
   201  			name:    "DeviceEnrollStatus",
   202  			nameMap: devicepb.DeviceEnrollStatus_name,
   203  			toString: func(i int32) string {
   204  				return ResourceDeviceEnrollStatusToString(devicepb.DeviceEnrollStatus(i))
   205  			},
   206  			fromString: func(s string) (int32, error) {
   207  				val, err := ResourceDeviceEnrollStatusFromString(s)
   208  				return int32(val), err
   209  			},
   210  		},
   211  		{
   212  			name:    "DeviceAttestationType",
   213  			nameMap: devicepb.DeviceAttestationType_name,
   214  			toString: func(i int32) string {
   215  				return ResourceDeviceAttestationTypeToString(devicepb.DeviceAttestationType(i))
   216  			},
   217  			fromString: func(s string) (int32, error) {
   218  				val, err := ResourceDeviceAttestationTypeFromString(s)
   219  				return int32(val), err
   220  			},
   221  		},
   222  		{
   223  			name:    "DeviceOrigin",
   224  			nameMap: devicepb.DeviceOrigin_name,
   225  			toString: func(i int32) string {
   226  				return ResourceDeviceOriginToString(devicepb.DeviceOrigin(i))
   227  			},
   228  			fromString: func(s string) (int32, error) {
   229  				val, err := ResourceDeviceOriginFromString(s)
   230  				return int32(val), err
   231  			},
   232  		},
   233  	}
   234  	for _, test := range tests {
   235  		t.Run(test.name, func(t *testing.T) {
   236  			for num, name := range test.nameMap {
   237  				t.Run(name, func(t *testing.T) {
   238  					s := test.toString(num)
   239  					gotNum, err := test.fromString(s)
   240  					require.NoError(t, err, "to/from enum conversion failed")
   241  					require.Equal(t, num, gotNum, "to/from enum conversion changed the enum value")
   242  				})
   243  			}
   244  
   245  			t.Run(`from "" (empty string)`, func(t *testing.T) {
   246  				got, err := test.fromString("")
   247  				require.NoError(t, err, `conversion from "" failed`)
   248  				require.Equal(t, int32(0), got, `conversion from "" returned a non-zero value`)
   249  			})
   250  		})
   251  	}
   252  }