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 }