github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/device.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  	"time"
    19  
    20  	"github.com/google/uuid"
    21  	"github.com/gravitational/trace"
    22  	"google.golang.org/protobuf/types/known/timestamppb"
    23  
    24  	devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
    25  )
    26  
    27  // CheckAndSetDefaults checks DeviceV1 fields to catch simple errors, and sets
    28  // default values for all fields with defaults.
    29  func (d *DeviceV1) CheckAndSetDefaults() error {
    30  	if d == nil {
    31  		return trace.BadParameter("device is nil")
    32  	}
    33  
    34  	// Assign defaults:
    35  	// - Kind = device
    36  	// - Metadata.Name = UUID
    37  	// - Spec.EnrollStatus = unspecified
    38  	// - Spec.Credential.AttestationType = unspecified
    39  	if d.Kind == "" {
    40  		d.Kind = KindDevice
    41  	} else if d.Kind != KindDevice { // sanity check
    42  		return trace.BadParameter("unexpected resource kind %q, must be %q", d.Kind, KindDevice)
    43  	}
    44  	if d.Metadata.Name == "" {
    45  		d.Metadata.Name = uuid.NewString()
    46  	}
    47  	if d.Spec.EnrollStatus == "" {
    48  		d.Spec.EnrollStatus = ResourceDeviceEnrollStatusToString(devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED)
    49  	}
    50  	if d.Spec.Credential != nil && d.Spec.Credential.DeviceAttestationType == "" {
    51  		d.Spec.Credential.DeviceAttestationType = ResourceDeviceAttestationTypeToString(devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_UNSPECIFIED)
    52  	}
    53  
    54  	// Validate Header/Metadata.
    55  	if err := d.ResourceHeader.CheckAndSetDefaults(); err != nil {
    56  		return trace.Wrap(err)
    57  	}
    58  
    59  	// Validate "simple" fields.
    60  	switch {
    61  	case d.Spec.OsType == "":
    62  		return trace.BadParameter("missing OS type")
    63  	case d.Spec.AssetTag == "":
    64  		return trace.BadParameter("missing asset tag")
    65  	}
    66  
    67  	// Validate enum conversions.
    68  	if _, err := ResourceOSTypeFromString(d.Spec.OsType); err != nil {
    69  		return trace.Wrap(err)
    70  	}
    71  	if _, err := ResourceDeviceEnrollStatusFromString(d.Spec.EnrollStatus); err != nil {
    72  		return trace.Wrap(err)
    73  	}
    74  	if d.Spec.Credential != nil {
    75  		if _, err := ResourceDeviceAttestationTypeFromString(d.Spec.Credential.DeviceAttestationType); err != nil {
    76  			return trace.Wrap(err)
    77  		}
    78  	}
    79  	if d.Spec.Source != nil {
    80  		if _, err := ResourceDeviceOriginFromString(d.Spec.Source.Origin); err != nil {
    81  			return trace.Wrap(err)
    82  		}
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  // DeviceFromResource converts a resource DeviceV1 to an API devicepb.Device.
    89  func DeviceFromResource(res *DeviceV1) (*devicepb.Device, error) {
    90  	if res == nil {
    91  		return nil, trace.BadParameter("device is nil")
    92  	}
    93  
    94  	toTimePB := func(t *time.Time) *timestamppb.Timestamp {
    95  		if t == nil {
    96  			return nil
    97  		}
    98  		return timestamppb.New(*t)
    99  	}
   100  
   101  	osType, err := ResourceOSTypeFromString(res.Spec.OsType)
   102  	if err != nil {
   103  		return nil, trace.Wrap(err)
   104  	}
   105  
   106  	enrollStatus, err := ResourceDeviceEnrollStatusFromString(res.Spec.EnrollStatus)
   107  	if err != nil {
   108  		return nil, trace.Wrap(err)
   109  	}
   110  
   111  	var cred *devicepb.DeviceCredential
   112  	if res.Spec.Credential != nil {
   113  		attestationType, err := ResourceDeviceAttestationTypeFromString(
   114  			res.Spec.Credential.DeviceAttestationType,
   115  		)
   116  		if err != nil {
   117  			return nil, trace.Wrap(err)
   118  		}
   119  		cred = &devicepb.DeviceCredential{
   120  			Id:                    res.Spec.Credential.Id,
   121  			PublicKeyDer:          res.Spec.Credential.PublicKeyDer,
   122  			DeviceAttestationType: attestationType,
   123  			TpmEkcertSerial:       res.Spec.Credential.TpmEkcertSerial,
   124  			TpmAkPublic:           res.Spec.Credential.TpmAkPublic,
   125  		}
   126  	}
   127  
   128  	collectedData := make([]*devicepb.DeviceCollectedData, len(res.Spec.CollectedData))
   129  	for i, d := range res.Spec.CollectedData {
   130  		dataOSType, err := ResourceOSTypeFromString(d.OsType)
   131  		if err != nil {
   132  			return nil, trace.Wrap(err)
   133  		}
   134  
   135  		collectedData[i] = &devicepb.DeviceCollectedData{
   136  			CollectTime:             toTimePB(d.CollectTime),
   137  			RecordTime:              toTimePB(d.RecordTime),
   138  			OsType:                  dataOSType,
   139  			SerialNumber:            d.SerialNumber,
   140  			ModelIdentifier:         d.ModelIdentifier,
   141  			OsVersion:               d.OsVersion,
   142  			OsBuild:                 d.OsBuild,
   143  			OsUsername:              d.OsUsername,
   144  			JamfBinaryVersion:       d.JamfBinaryVersion,
   145  			MacosEnrollmentProfiles: d.MacosEnrollmentProfiles,
   146  			ReportedAssetTag:        d.ReportedAssetTag,
   147  			SystemSerialNumber:      d.SystemSerialNumber,
   148  			BaseBoardSerialNumber:   d.BaseBoardSerialNumber,
   149  			TpmPlatformAttestation: tpmPlatformAttestationFromResource(
   150  				d.TpmPlatformAttestation,
   151  			),
   152  			OsId: d.OsId,
   153  		}
   154  	}
   155  
   156  	var source *devicepb.DeviceSource
   157  	if s := res.Spec.Source; s != nil {
   158  		origin, err := ResourceDeviceOriginFromString(s.Origin)
   159  		if err != nil {
   160  			return nil, trace.Wrap(err)
   161  		}
   162  		source = &devicepb.DeviceSource{
   163  			Name:   s.Name,
   164  			Origin: origin,
   165  		}
   166  	}
   167  
   168  	var profile *devicepb.DeviceProfile
   169  	if p := res.Spec.Profile; p != nil {
   170  		profile = &devicepb.DeviceProfile{
   171  			UpdateTime:          toTimePB(p.UpdateTime),
   172  			ModelIdentifier:     p.ModelIdentifier,
   173  			OsVersion:           p.OsVersion,
   174  			OsBuild:             p.OsBuild,
   175  			OsBuildSupplemental: p.OsBuildSupplemental,
   176  			OsUsernames:         p.OsUsernames,
   177  			JamfBinaryVersion:   p.JamfBinaryVersion,
   178  			ExternalId:          p.ExternalId,
   179  			OsId:                p.OsId,
   180  		}
   181  	}
   182  
   183  	return &devicepb.Device{
   184  		ApiVersion:    res.Version,
   185  		Id:            res.Metadata.Name,
   186  		OsType:        osType,
   187  		AssetTag:      res.Spec.AssetTag,
   188  		CreateTime:    toTimePB(res.Spec.CreateTime),
   189  		UpdateTime:    toTimePB(res.Spec.UpdateTime),
   190  		EnrollStatus:  enrollStatus,
   191  		Credential:    cred,
   192  		CollectedData: collectedData,
   193  		Source:        source,
   194  		Profile:       profile,
   195  		Owner:         res.Spec.Owner,
   196  	}, nil
   197  }
   198  
   199  // DeviceToResource converts an API devicepb.Device to a resource DeviceV1 and
   200  // assigns all default fields.
   201  func DeviceToResource(dev *devicepb.Device) *DeviceV1 {
   202  	if dev == nil {
   203  		return nil
   204  	}
   205  
   206  	toTimePtr := func(pb *timestamppb.Timestamp) *time.Time {
   207  		if pb == nil {
   208  			return nil
   209  		}
   210  		t := pb.AsTime()
   211  		return &t
   212  	}
   213  
   214  	var cred *DeviceCredential
   215  	if dev.Credential != nil {
   216  		cred = &DeviceCredential{
   217  			Id:           dev.Credential.Id,
   218  			PublicKeyDer: dev.Credential.PublicKeyDer,
   219  			DeviceAttestationType: ResourceDeviceAttestationTypeToString(
   220  				dev.Credential.DeviceAttestationType,
   221  			),
   222  			TpmEkcertSerial: dev.Credential.TpmEkcertSerial,
   223  			TpmAkPublic:     dev.Credential.TpmAkPublic,
   224  		}
   225  	}
   226  
   227  	collectedData := make([]*DeviceCollectedData, len(dev.CollectedData))
   228  	for i, d := range dev.CollectedData {
   229  		collectedData[i] = &DeviceCollectedData{
   230  			CollectTime:             toTimePtr(d.CollectTime),
   231  			RecordTime:              toTimePtr(d.RecordTime),
   232  			OsType:                  ResourceOSTypeToString(d.OsType),
   233  			SerialNumber:            d.SerialNumber,
   234  			ModelIdentifier:         d.ModelIdentifier,
   235  			OsVersion:               d.OsVersion,
   236  			OsBuild:                 d.OsBuild,
   237  			OsUsername:              d.OsUsername,
   238  			JamfBinaryVersion:       d.JamfBinaryVersion,
   239  			MacosEnrollmentProfiles: d.MacosEnrollmentProfiles,
   240  			ReportedAssetTag:        d.ReportedAssetTag,
   241  			SystemSerialNumber:      d.SystemSerialNumber,
   242  			BaseBoardSerialNumber:   d.BaseBoardSerialNumber,
   243  			TpmPlatformAttestation: tpmPlatformAttestationToResource(
   244  				d.TpmPlatformAttestation,
   245  			),
   246  			OsId: d.OsId,
   247  		}
   248  	}
   249  
   250  	var source *DeviceSource
   251  	if s := dev.Source; s != nil {
   252  		source = &DeviceSource{
   253  			Name:   s.Name,
   254  			Origin: ResourceDeviceOriginToString(s.Origin),
   255  		}
   256  	}
   257  
   258  	var profile *DeviceProfile
   259  	if p := dev.Profile; p != nil {
   260  		profile = &DeviceProfile{
   261  			UpdateTime:          toTimePtr(p.UpdateTime),
   262  			ModelIdentifier:     p.ModelIdentifier,
   263  			OsVersion:           p.OsVersion,
   264  			OsBuild:             p.OsBuild,
   265  			OsBuildSupplemental: p.OsBuildSupplemental,
   266  			OsUsernames:         p.OsUsernames,
   267  			JamfBinaryVersion:   p.JamfBinaryVersion,
   268  			ExternalId:          p.ExternalId,
   269  			OsId:                p.OsId,
   270  		}
   271  	}
   272  
   273  	res := &DeviceV1{
   274  		ResourceHeader: ResourceHeader{
   275  			Kind:    KindDevice,
   276  			Version: dev.ApiVersion,
   277  			Metadata: Metadata{
   278  				Name: dev.Id,
   279  			},
   280  		},
   281  		Spec: &DeviceSpec{
   282  			OsType:        ResourceOSTypeToString(dev.OsType),
   283  			AssetTag:      dev.AssetTag,
   284  			CreateTime:    toTimePtr(dev.CreateTime),
   285  			UpdateTime:    toTimePtr(dev.UpdateTime),
   286  			EnrollStatus:  ResourceDeviceEnrollStatusToString(dev.EnrollStatus),
   287  			Credential:    cred,
   288  			CollectedData: collectedData,
   289  			Source:        source,
   290  			Profile:       profile,
   291  			Owner:         dev.Owner,
   292  		},
   293  	}
   294  	_ = res.CheckAndSetDefaults() // assign default fields
   295  	return res
   296  }
   297  
   298  func tpmPlatformAttestationToResource(pa *devicepb.TPMPlatformAttestation) *TPMPlatformAttestation {
   299  	if pa == nil {
   300  		return nil
   301  	}
   302  
   303  	var outPlatParams *TPMPlatformParameters
   304  	if pa.PlatformParameters != nil {
   305  		var quotes []*TPMQuote
   306  		for _, q := range pa.PlatformParameters.Quotes {
   307  			quotes = append(quotes, &TPMQuote{
   308  				Quote:     q.Quote,
   309  				Signature: q.Signature,
   310  			})
   311  		}
   312  
   313  		var pcrs []*TPMPCR
   314  		for _, pcr := range pa.PlatformParameters.Pcrs {
   315  			pcrs = append(pcrs, &TPMPCR{
   316  				Index:     pcr.Index,
   317  				Digest:    pcr.Digest,
   318  				DigestAlg: pcr.DigestAlg,
   319  			})
   320  		}
   321  
   322  		outPlatParams = &TPMPlatformParameters{
   323  			Quotes:   quotes,
   324  			Pcrs:     pcrs,
   325  			EventLog: pa.PlatformParameters.EventLog,
   326  		}
   327  	}
   328  
   329  	return &TPMPlatformAttestation{
   330  		Nonce:              pa.Nonce,
   331  		PlatformParameters: outPlatParams,
   332  	}
   333  }
   334  
   335  func tpmPlatformAttestationFromResource(pa *TPMPlatformAttestation) *devicepb.TPMPlatformAttestation {
   336  	if pa == nil {
   337  		return nil
   338  	}
   339  
   340  	var outPlatParams *devicepb.TPMPlatformParameters
   341  	if pa.PlatformParameters != nil {
   342  		var quotes []*devicepb.TPMQuote
   343  		for _, q := range pa.PlatformParameters.Quotes {
   344  			quotes = append(quotes, &devicepb.TPMQuote{
   345  				Quote:     q.Quote,
   346  				Signature: q.Signature,
   347  			})
   348  		}
   349  
   350  		var pcrs []*devicepb.TPMPCR
   351  		for _, pcr := range pa.PlatformParameters.Pcrs {
   352  			pcrs = append(pcrs, &devicepb.TPMPCR{
   353  				Index:     pcr.Index,
   354  				Digest:    pcr.Digest,
   355  				DigestAlg: pcr.DigestAlg,
   356  			})
   357  		}
   358  
   359  		outPlatParams = &devicepb.TPMPlatformParameters{
   360  			Quotes:   quotes,
   361  			EventLog: pa.PlatformParameters.EventLog,
   362  			Pcrs:     pcrs,
   363  		}
   364  	}
   365  
   366  	return &devicepb.TPMPlatformAttestation{
   367  		Nonce:              pa.Nonce,
   368  		PlatformParameters: outPlatParams,
   369  	}
   370  }
   371  
   372  // ResourceOSTypeToString converts OSType to a string representation suitable
   373  // for use in resource fields.
   374  func ResourceOSTypeToString(osType devicepb.OSType) string {
   375  	switch osType {
   376  	case devicepb.OSType_OS_TYPE_UNSPECIFIED:
   377  		return "unspecified"
   378  	case devicepb.OSType_OS_TYPE_LINUX:
   379  		return "linux"
   380  	case devicepb.OSType_OS_TYPE_MACOS:
   381  		return "macos"
   382  	case devicepb.OSType_OS_TYPE_WINDOWS:
   383  		return "windows"
   384  	default:
   385  		return osType.String()
   386  	}
   387  }
   388  
   389  // ResourceOSTypeFromString converts a string representation of OSType suitable
   390  // for resource fields to OSType.
   391  func ResourceOSTypeFromString(osType string) (devicepb.OSType, error) {
   392  	switch osType {
   393  	case "", "unspecified":
   394  		return devicepb.OSType_OS_TYPE_UNSPECIFIED, nil
   395  	case "linux":
   396  		return devicepb.OSType_OS_TYPE_LINUX, nil
   397  	case "macos":
   398  		return devicepb.OSType_OS_TYPE_MACOS, nil
   399  	case "windows":
   400  		return devicepb.OSType_OS_TYPE_WINDOWS, nil
   401  	default:
   402  		return devicepb.OSType_OS_TYPE_UNSPECIFIED, trace.BadParameter("unknown os type %q", osType)
   403  	}
   404  }
   405  
   406  // ResourceDeviceEnrollStatusToString converts DeviceEnrollStatus to a string
   407  // representation suitable for use in resource fields.
   408  func ResourceDeviceEnrollStatusToString(enrollStatus devicepb.DeviceEnrollStatus) string {
   409  	switch enrollStatus {
   410  	case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED:
   411  		return "enrolled"
   412  	case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_NOT_ENROLLED:
   413  		return "not_enrolled"
   414  	case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED:
   415  		return "unspecified"
   416  	default:
   417  		return enrollStatus.String()
   418  	}
   419  }
   420  
   421  // ResourceDeviceEnrollStatusFromString converts a string representation of
   422  // DeviceEnrollStatus suitable for resource fields to DeviceEnrollStatus.
   423  func ResourceDeviceEnrollStatusFromString(enrollStatus string) (devicepb.DeviceEnrollStatus, error) {
   424  	switch enrollStatus {
   425  	case "enrolled":
   426  		return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED, nil
   427  	case "not_enrolled":
   428  		return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_NOT_ENROLLED, nil
   429  	case "unspecified":
   430  		return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, nil
   431  	// In the terraform provider, enroll_status is an optional field and can be empty.
   432  	case "":
   433  		return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, nil
   434  	default:
   435  		return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, trace.BadParameter("unknown enroll status %q", enrollStatus)
   436  	}
   437  }
   438  
   439  func ResourceDeviceAttestationTypeToString(
   440  	attestationType devicepb.DeviceAttestationType,
   441  ) string {
   442  	switch attestationType {
   443  	case devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_UNSPECIFIED:
   444  		// Default to empty, so it doesn't show in non-TPM devices.
   445  		return ""
   446  	case devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_TPM_EKPUB:
   447  		return "tpm_ekpub"
   448  	case devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_TPM_EKCERT:
   449  		return "tpm_ekcert"
   450  	case devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_TPM_EKCERT_TRUSTED:
   451  		return "tpm_ekcert_trusted"
   452  	default:
   453  		return attestationType.String()
   454  	}
   455  }
   456  
   457  func ResourceDeviceAttestationTypeFromString(
   458  	attestationType string,
   459  ) (devicepb.DeviceAttestationType, error) {
   460  	switch attestationType {
   461  	case "unspecified", "":
   462  		return devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_UNSPECIFIED, nil
   463  	case "tpm_ekpub":
   464  		return devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_TPM_EKPUB, nil
   465  	case "tpm_ekcert":
   466  		return devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_TPM_EKCERT, nil
   467  	case "tpm_ekcert_trusted":
   468  		return devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_TPM_EKCERT_TRUSTED, nil
   469  	default:
   470  		return devicepb.DeviceAttestationType_DEVICE_ATTESTATION_TYPE_UNSPECIFIED, trace.BadParameter("unknown attestation type %q", attestationType)
   471  	}
   472  }
   473  
   474  func ResourceDeviceOriginToString(o devicepb.DeviceOrigin) string {
   475  	switch o {
   476  	case devicepb.DeviceOrigin_DEVICE_ORIGIN_UNSPECIFIED:
   477  		return "unspecified"
   478  	case devicepb.DeviceOrigin_DEVICE_ORIGIN_API:
   479  		return "api"
   480  	case devicepb.DeviceOrigin_DEVICE_ORIGIN_JAMF:
   481  		return "jamf"
   482  	case devicepb.DeviceOrigin_DEVICE_ORIGIN_INTUNE:
   483  		return "intune"
   484  	default:
   485  		return o.String()
   486  	}
   487  }
   488  
   489  func ResourceDeviceOriginFromString(s string) (devicepb.DeviceOrigin, error) {
   490  	switch s {
   491  	case "", "unspecified":
   492  		return devicepb.DeviceOrigin_DEVICE_ORIGIN_UNSPECIFIED, nil
   493  	case "api":
   494  		return devicepb.DeviceOrigin_DEVICE_ORIGIN_API, nil
   495  	case "jamf":
   496  		return devicepb.DeviceOrigin_DEVICE_ORIGIN_JAMF, nil
   497  	case "intune":
   498  		return devicepb.DeviceOrigin_DEVICE_ORIGIN_INTUNE, nil
   499  	default:
   500  		return devicepb.DeviceOrigin_DEVICE_ORIGIN_UNSPECIFIED, trace.BadParameter("unknown device origin %q", s)
   501  	}
   502  }