-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Define an explicit device resource as DeviceV1 #23901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
1304589
Define the DeviceV1 resource proto
codingllama a9af051
Update generated protos
codingllama 705684b
Move device conversions to API
codingllama 3254993
Test device conversions
codingllama 9a5b897
Handle nil gracefully
codingllama 1121098
Add license to new files
codingllama File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // Copyright 2023 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. | ||
|
|
||
| syntax = "proto3"; | ||
|
|
||
| package types; | ||
|
|
||
| import "gogoproto/gogo.proto"; | ||
| import "google/protobuf/timestamp.proto"; | ||
| import "teleport/legacy/types/types.proto"; | ||
|
|
||
| option go_package = "github.com/gravitational/teleport/api/types"; | ||
| option (gogoproto.goproto_getters_all) = false; | ||
| option (gogoproto.marshaler_all) = true; | ||
| option (gogoproto.unmarshaler_all) = true; | ||
|
|
||
| // DeviceV1 is the resource representation of teleport.devicetrust.v1.Device. | ||
| message DeviceV1 { | ||
| // Header is the common resource header. | ||
| // | ||
| // - Kind is always "device". | ||
| // - SubKind is unused. | ||
| // - Version is equivalent to teleport.devicetrust.v1.Device.api_version. | ||
| // - Metadata.Name is equivalent to teleport.devicetrust.v1.Device.Id. | ||
| ResourceHeader Header = 1 [ | ||
| (gogoproto.nullable) = false, | ||
| (gogoproto.jsontag) = "", | ||
| (gogoproto.embed) = true | ||
| ]; | ||
| // Specification of the device. | ||
| DeviceSpec spec = 5 [(gogoproto.jsontag) = "spec"]; | ||
| } | ||
|
|
||
| // DeviceSpec is a device specification. | ||
| // Roughly matches teleport.devicetrust.v1.Device, with some fields changed for | ||
| // better UX. | ||
| message DeviceSpec { | ||
| string os_type = 1 [(gogoproto.jsontag) = "os_type"]; | ||
| string asset_tag = 2 [(gogoproto.jsontag) = "asset_tag"]; | ||
| google.protobuf.Timestamp create_time = 3 [ | ||
| (gogoproto.stdtime) = true, | ||
| (gogoproto.jsontag) = "create_time" | ||
| ]; | ||
| google.protobuf.Timestamp update_time = 4 [ | ||
| (gogoproto.stdtime) = true, | ||
| (gogoproto.jsontag) = "update_time" | ||
| ]; | ||
| string enroll_status = 5 [(gogoproto.jsontag) = "enroll_status"]; | ||
| DeviceCredential credential = 6 [(gogoproto.jsontag) = "credential,omitempty"]; | ||
| repeated DeviceCollectedData collected_data = 7 [(gogoproto.jsontag) = "collected_data,omitempty"]; | ||
| } | ||
|
|
||
| // DeviceCredential is the resource representation of | ||
| // teleport.devicetrust.v1.DeviceCredential. | ||
| message DeviceCredential { | ||
| string id = 1 [(gogoproto.jsontag) = "id"]; | ||
| bytes public_key_der = 2 [(gogoproto.jsontag) = "public_key_der"]; | ||
| } | ||
|
|
||
| // DeviceCollectedData is the resource representation of | ||
| // teleport.devicetrust.v1.DeviceCollectedData. | ||
| message DeviceCollectedData { | ||
| google.protobuf.Timestamp collect_time = 1 [ | ||
| (gogoproto.stdtime) = true, | ||
| (gogoproto.jsontag) = "collect_time" | ||
| ]; | ||
| google.protobuf.Timestamp record_time = 2 [ | ||
| (gogoproto.stdtime) = true, | ||
| (gogoproto.jsontag) = "record_time" | ||
| ]; | ||
| string os_type = 3 [(gogoproto.jsontag) = "os_type"]; | ||
| string serial_number = 4 [(gogoproto.jsontag) = "serial_number,omitempty"]; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| // Copyright 2023 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 types | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "time" | ||
|
|
||
| "github.com/google/uuid" | ||
| "github.com/gravitational/trace" | ||
| "google.golang.org/protobuf/types/known/timestamppb" | ||
|
|
||
| devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" | ||
| ) | ||
|
|
||
| // CheckAndSetDefaults checks DeviceV1 fields to catch simple errors, and sets | ||
| // default values for all fields with defaults. | ||
| func (d *DeviceV1) CheckAndSetDefaults() error { | ||
| if d == nil { | ||
| return trace.BadParameter("device is nil") | ||
| } | ||
|
|
||
| // Assign defaults: | ||
| // - Kind = device | ||
| // - Metadata.Name = UUID | ||
| // - Spec.EnrollStatus = unspecified | ||
| if d.Kind == "" { | ||
| d.Kind = KindDevice | ||
| } else if d.Kind != KindDevice { // sanity check | ||
| return trace.BadParameter("unexpected resource kind %q, must be %q", d.Kind, KindDevice) | ||
| } | ||
| if d.Metadata.Name == "" { | ||
| d.Metadata.Name = uuid.NewString() | ||
| } | ||
| if d.Spec.EnrollStatus == "" { | ||
| d.Spec.EnrollStatus = ResourceEnrollStatusToString(devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED) | ||
| } | ||
|
|
||
| // Validate Header/Metadata. | ||
| if err := d.ResourceHeader.CheckAndSetDefaults(); err != nil { | ||
| return trace.Wrap(err) | ||
| } | ||
|
|
||
| // Validate "simple" fields. | ||
| switch { | ||
| case d.Spec.OsType == "": | ||
| return trace.BadParameter("missing OS type") | ||
| case d.Spec.AssetTag == "": | ||
| return trace.BadParameter("missing asset tag") | ||
| } | ||
|
|
||
| // Validate enum conversions. | ||
| if _, err := ResourceOSTypeFromString(d.Spec.OsType); err != nil { | ||
| return trace.Wrap(err) | ||
| } | ||
| if _, err := ResourceEnrollStatusFromString(d.Spec.EnrollStatus); err != nil { | ||
| return trace.Wrap(err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // UnmarshalDevice unmarshals a DeviceV1 resource and runs CheckAndSetDefaults. | ||
| func UnmarshalDevice(raw []byte) (*DeviceV1, error) { | ||
|
codingllama marked this conversation as resolved.
Outdated
|
||
| dev := &DeviceV1{} | ||
| if err := json.Unmarshal(raw, dev); err != nil { | ||
| return nil, trace.Wrap(err) | ||
| } | ||
| return dev, trace.Wrap(dev.CheckAndSetDefaults()) | ||
| } | ||
|
|
||
| // DeviceFromResource converts a resource DeviceV1 to an API devicepb.Device. | ||
| func DeviceFromResource(res *DeviceV1) (*devicepb.Device, error) { | ||
|
codingllama marked this conversation as resolved.
Outdated
|
||
| if res == nil { | ||
| return nil, trace.BadParameter("device is nil") | ||
| } | ||
|
|
||
| toTimePB := func(t *time.Time) *timestamppb.Timestamp { | ||
| if t == nil { | ||
| return nil | ||
| } | ||
| return timestamppb.New(*t) | ||
| } | ||
|
|
||
| osType, err := ResourceOSTypeFromString(res.Spec.OsType) | ||
| if err != nil { | ||
| return nil, trace.Wrap(err) | ||
| } | ||
|
|
||
| enrollStatus, err := ResourceEnrollStatusFromString(res.Spec.EnrollStatus) | ||
| if err != nil { | ||
| return nil, trace.Wrap(err) | ||
| } | ||
|
|
||
| var cred *devicepb.DeviceCredential | ||
| if res.Spec.Credential != nil { | ||
| cred = &devicepb.DeviceCredential{ | ||
| Id: res.Spec.Credential.Id, | ||
| PublicKeyDer: res.Spec.Credential.PublicKeyDer, | ||
| } | ||
| } | ||
|
|
||
| collectedData := make([]*devicepb.DeviceCollectedData, len(res.Spec.CollectedData)) | ||
| for i, d := range res.Spec.CollectedData { | ||
| dataOSType, err := ResourceOSTypeFromString(d.OsType) | ||
| if err != nil { | ||
| return nil, trace.Wrap(err) | ||
| } | ||
|
|
||
| collectedData[i] = &devicepb.DeviceCollectedData{ | ||
| CollectTime: toTimePB(d.CollectTime), | ||
| RecordTime: toTimePB(d.RecordTime), | ||
| OsType: dataOSType, | ||
| SerialNumber: d.SerialNumber, | ||
| } | ||
| } | ||
|
|
||
| return &devicepb.Device{ | ||
| ApiVersion: res.Version, | ||
| Id: res.Metadata.Name, | ||
| OsType: osType, | ||
| AssetTag: res.Spec.AssetTag, | ||
| CreateTime: toTimePB(res.Spec.CreateTime), | ||
| UpdateTime: toTimePB(res.Spec.UpdateTime), | ||
| EnrollStatus: enrollStatus, | ||
| Credential: cred, | ||
| CollectedData: collectedData, | ||
| }, nil | ||
| } | ||
|
|
||
| // DeviceToResource converts an API devicepb.Device to a resource DeviceV1 and | ||
| // assigns all default fields. | ||
| func DeviceToResource(dev *devicepb.Device) *DeviceV1 { | ||
|
codingllama marked this conversation as resolved.
Outdated
|
||
| if dev == nil { | ||
| return nil | ||
| } | ||
|
|
||
| toTimePtr := func(pb *timestamppb.Timestamp) *time.Time { | ||
| if pb == nil { | ||
| return nil | ||
| } | ||
| t := pb.AsTime() | ||
| return &t | ||
| } | ||
|
|
||
| var cred *DeviceCredential | ||
| if dev.Credential != nil { | ||
| cred = &DeviceCredential{ | ||
| Id: dev.Credential.Id, | ||
| PublicKeyDer: dev.Credential.PublicKeyDer, | ||
| } | ||
| } | ||
|
|
||
| collectedData := make([]*DeviceCollectedData, len(dev.CollectedData)) | ||
| for i, d := range dev.CollectedData { | ||
| collectedData[i] = &DeviceCollectedData{ | ||
| CollectTime: toTimePtr(d.CollectTime), | ||
| RecordTime: toTimePtr(d.RecordTime), | ||
| OsType: ResourceOSTypeToString(d.OsType), | ||
| SerialNumber: d.SerialNumber, | ||
| } | ||
| } | ||
|
|
||
| res := &DeviceV1{ | ||
| ResourceHeader: ResourceHeader{ | ||
| Kind: KindDevice, | ||
| Version: dev.ApiVersion, | ||
| Metadata: Metadata{ | ||
| Name: dev.Id, | ||
| }, | ||
| }, | ||
| Spec: &DeviceSpec{ | ||
| OsType: ResourceOSTypeToString(dev.OsType), | ||
| AssetTag: dev.AssetTag, | ||
| CreateTime: toTimePtr(dev.CreateTime), | ||
| UpdateTime: toTimePtr(dev.UpdateTime), | ||
| EnrollStatus: ResourceEnrollStatusToString(dev.EnrollStatus), | ||
| Credential: cred, | ||
| CollectedData: collectedData, | ||
| }, | ||
| } | ||
| _ = res.CheckAndSetDefaults() // assign default fields | ||
| return res | ||
| } | ||
|
|
||
| // ResourceOSTypeToString converts OSType to a string representation suitable | ||
| // for use in resource fields. | ||
| func ResourceOSTypeToString(osType devicepb.OSType) string { | ||
|
codingllama marked this conversation as resolved.
Outdated
|
||
| switch osType { | ||
| case devicepb.OSType_OS_TYPE_LINUX: | ||
| return "linux" | ||
| case devicepb.OSType_OS_TYPE_MACOS: | ||
| return "macos" | ||
| case devicepb.OSType_OS_TYPE_WINDOWS: | ||
| return "windows" | ||
| default: | ||
| return osType.String() | ||
| } | ||
| } | ||
|
|
||
| // ResourceOSTypeFromString converts a string representation of OSType suitable | ||
| // for resource fields to OSType. | ||
| func ResourceOSTypeFromString(osType string) (devicepb.OSType, error) { | ||
| switch osType { | ||
| case "linux": | ||
| return devicepb.OSType_OS_TYPE_LINUX, nil | ||
| case "macos": | ||
| return devicepb.OSType_OS_TYPE_MACOS, nil | ||
| case "windows": | ||
| return devicepb.OSType_OS_TYPE_WINDOWS, nil | ||
| default: | ||
| return devicepb.OSType_OS_TYPE_UNSPECIFIED, trace.BadParameter("unknown os type %q", osType) | ||
| } | ||
| } | ||
|
|
||
| // ResourceEnrollStatusToString converts DeviceEnrollStatus to a string | ||
| // representation suitable for use in resource fields. | ||
| func ResourceEnrollStatusToString(enrollStatus devicepb.DeviceEnrollStatus) string { | ||
| switch enrollStatus { | ||
| case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED: | ||
| return "enrolled" | ||
| case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_NOT_ENROLLED: | ||
| return "not_enrolled" | ||
| case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED: | ||
| return "unspecified" | ||
| default: | ||
| return enrollStatus.String() | ||
| } | ||
| } | ||
|
|
||
| // ResourceEnrollStatusFromString converts a string representation of | ||
| // DeviceEnrollStatus suitable for resource fields to DeviceEnrollStatus. | ||
| func ResourceEnrollStatusFromString(enrollStatus string) (devicepb.DeviceEnrollStatus, error) { | ||
| switch enrollStatus { | ||
| case "enrolled": | ||
| return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED, nil | ||
| case "not_enrolled": | ||
| return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_NOT_ENROLLED, nil | ||
| case "unspecified": | ||
| return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, nil | ||
| default: | ||
| return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, trace.BadParameter("unknown enroll status %q", enrollStatus) | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.