diff --git a/benchmarking/locust/common/ateapi_pb2.py b/benchmarking/locust/common/ateapi_pb2.py index 576745cc7d..cb1229c3cf 100644 --- a/benchmarking/locust/common/ateapi_pb2.py +++ b/benchmarking/locust/common/ateapi_pb2.py @@ -40,7 +40,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x61teapi.proto\x12\x06\x61teapi\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"]\n\x10\x45xternalSnapshot\x12\x14\n\x0csnapshot_uri\x18\x01 \x01(\t\x12\x33\n\rcontent_scope\x18\x02 \x01(\x0e\x32\x1c.ateapi.SnapshotContentScope\"\x86\x01\n\x11LocalSnapshotInfo\x12\x15\n\rsnapshot_name\x18\x01 \x01(\t\x12%\n\x1dnode_vms_with_local_snapshots\x18\x02 \x03(\t\x12\x33\n\rcontent_scope\x18\x03 \x01(\x0e\x32\x1c.ateapi.SnapshotContentScope\"w\n\x08Selector\x12\x37\n\x0cmatch_labels\x18\x01 \x03(\x0b\x32!.ateapi.Selector.MatchLabelsEntry\x1a\x32\n\x10MatchLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb2\x01\n\x10ResourceMetadata\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0b\n\x03uid\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\x03\x12/\n\x0b\x63reate_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0bupdate_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xdc\x02\n\x0e\x45xternalVolume\x12\x13\n\x0bvolume_name\x18\x01 \x01(\t\x12\x19\n\x11storage_volume_id\x18\x02 \x01(\t\x12\x13\n\x0bvolume_type\x18\x03 \x01(\t\x12-\n\x06status\x18\x04 \x01(\x0e\x32\x1d.ateapi.ExternalVolume.Status\x12\x41\n\x0evolume_context\x18\x05 \x03(\x0b\x32).ateapi.ExternalVolume.VolumeContextEntry\x1a\x34\n\x12VolumeContextEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"]\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n\x0eSTATUS_PENDING\x10\x01\x12\x12\n\x0eSTATUS_CREATED\x10\x02\x12\x13\n\x0fSTATUS_DELETING\x10\x03\"\xd5\x01\n\x05\x41\x63tor\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12)\n\x0e\x61\x63tor_template\x18\x04 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12)\n\x0fworker_selector\x18\x05 \x01(\x0b\x32\x10.ateapi.Selector\x12%\n\nsource_tag\x18\x06 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12#\n\x06status\x18\x07 \x01(\x0b\x32\x13.ateapi.ActorStatus\"]\n\x0c\x45gressPolicy\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12!\n\x05rules\x18\x02 \x03(\x0b\x32\x12.ateapi.EgressRule\"\x82\x01\n\nEgressRule\x12\'\n\thostnames\x18\x01 \x01(\x0b\x32\x14.ateapi.HostnameRule\x12&\n\tip_blocks\x18\x02 \x01(\x0b\x32\x13.ateapi.IPBlockRule\x12#\n\x03\x61ll\x18\x03 \x01(\x0b\x32\x16.google.protobuf.Empty\"L\n\x0cHostnameRule\x12\x10\n\x08patterns\x18\x01 \x03(\t\x12*\n\x07\x65\x66\x66\x65\x63ts\x18\x02 \x01(\x0b\x32\x19.ateapi.EgressRuleEffects\"\x1c\n\x0bIPBlockRule\x12\r\n\x05\x63idrs\x18\x01 \x03(\t\"U\n\x11\x45gressRuleEffects\x12@\n\x15inject_static_headers\x18\x01 \x03(\x0b\x32!.ateapi.CredentialHeaderInjection\"S\n\x19\x43redentialHeaderInjection\x12\x0e\n\x06header\x18\x01 \x01(\t\x12\x0e\n\x06prefix\x18\x02 \x01(\t\x12\x16\n\x0e\x63redential_uri\x18\x03 \x01(\t\"\xf1\x02\n\x0b\x41\x63torStatus\x12!\n\x05state\x18\x01 \x01(\x0e\x32\x12.ateapi.ActorState\x12\x33\n\x11worker_assignment\x18\x02 \x01(\x0b\x32\x18.ateapi.WorkerAssignment\x12!\n\x19in_progress_snapshot_name\x18\x03 \x01(\t\x12\x33\n\x11\x65xternal_snapshot\x18\x04 \x01(\x0b\x32\x18.ateapi.ExternalSnapshot\x12\x36\n\x13local_snapshot_info\x18\x05 \x01(\x0b\x32\x19.ateapi.LocalSnapshotInfo\x12-\n\ractor_volumes\x18\x07 \x03(\x0b\x32\x16.ateapi.ExternalVolume\x12\'\n\x1fin_progress_local_snapshot_name\x18\x08 \x01(\t\x12\"\n\x1a\x63urrent_actor_template_uid\x18\x0b \x01(\t\"\xa7\x01\n\x10WorkerAssignment\x12!\n\x06worker\x18\x06 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x18\n\x10worker_namespace\x18\x01 \x01(\t\x12\x13\n\x0bworker_pool\x18\x02 \x01(\t\x12\x12\n\nworker_pod\x18\x03 \x01(\t\x12\x16\n\x0eworker_pod_uid\x18\x04 \x01(\t\x12\x15\n\rworker_pod_ip\x18\x05 \x01(\t\"\x8f\x01\n\tTagStatus\x12*\n\x08snapshot\x18\x01 \x01(\x0b\x32\x18.ateapi.ExternalSnapshot\x12\x1a\n\x12\x61\x63tor_template_uid\x18\x02 \x01(\t\x12 \n\x18in_progress_snapshot_uri\x18\x03 \x01(\t\x12\x18\n\x10source_actor_uid\x18\x04 \x01(\t\"\x9e\x01\n\x03Tag\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12!\n\x06status\x18\x02 \x01(\x0b\x32\x11.ateapi.TagStatus\x12\x1f\n\x05scope\x18\x03 \x01(\x0e\x32\x10.ateapi.TagScope\x12\'\n\x0csource_actor\x18\x04 \x01(\x0b\x32\x11.ateapi.ObjectRef\"6\n\x08\x41tespace\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\"+\n\tObjectRef\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xe3\x02\n\rActorTemplate\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12)\n\x0fworker_selector\x18\x02 \x01(\x0b\x32\x10.ateapi.Selector\x12%\n\ncontainers\x18\x03 \x03(\x0b\x32\x11.ateapi.Container\x12\x1f\n\x07volumes\x18\x04 \x03(\x0b\x32\x0e.ateapi.Volume\x12\x31\n\x10snapshots_config\x18\x05 \x01(\x0b\x32\x17.ateapi.SnapshotsConfig\x12-\n\x0esandbox_config\x18\x06 \x01(\x0b\x32\x15.ateapi.SandboxConfig\x12$\n\tresources\x18\x07 \x01(\x0b\x32\x11.ateapi.Resources\x12+\n\x06status\x18\x08 \x01(\x0b\x32\x1b.ateapi.ActorTemplateStatus\"+\n\tResources\x12\x1e\n\x06limits\x18\x01 \x03(\x0b\x32\x0e.ateapi.Limits\"(\n\x06Limits\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08quantity\x18\x02 \x01(\t\"\x9d\x01\n\x14GoldenSnapshotStatus\x12\x31\n\x0fgolden_snapshot\x18\x01 \x01(\x0b\x32\x18.ateapi.ExternalSnapshot\x12;\n\x17take_golden_snapshot_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x15\n\rerror_message\x18\x03 \x01(\t\"S\n\x13\x41\x63torTemplateStatus\x12<\n\x16golden_snapshot_status\x18\x01 \x01(\x0b\x32\x1c.ateapi.GoldenSnapshotStatus\"Q\n\rSandboxConfig\x12+\n\rsandbox_class\x18\x01 \x01(\x0e\x32\x14.ateapi.SandboxClass\x12\x13\n\x0b\x63onfig_name\x18\x02 \x01(\t\"\xb7\x01\n\x0fSnapshotsConfig\x12.\n\x08on_pause\x18\x01 \x01(\x0e\x32\x1c.ateapi.SnapshotContentScope\x12/\n\ton_commit\x18\x02 \x01(\x0e\x32\x1c.ateapi.SnapshotContentScope\x12)\n\ton_resume\x18\x03 \x01(\x0b\x32\x16.ateapi.OnResumeConfig\x12\x18\n\x10storage_location\x18\x04 \x01(\t\"9\n\x0eOnResumeConfig\x12\'\n\tfrom_data\x18\x01 \x01(\x0e\x32\x14.ateapi.ResumeSource\"\x92\x02\n\tContainer\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05image\x18\x02 \x01(\t\x12\x0f\n\x07\x63ommand\x18\x03 \x03(\t\x12\x0c\n\x04\x61rgs\x18\x04 \x03(\t\x12\x1b\n\x03\x65nv\x18\x05 \x03(\x0b\x32\x0e.ateapi.EnvVar\x12\'\n\x06readyz\x18\x06 \x01(\x0b\x32\x17.ateapi.ContainerReadyz\x12*\n\rvolume_mounts\x18\x07 \x03(\x0b\x32\x13.ateapi.VolumeMount\x12\x31\n\x10security_context\x18\x08 \x01(\x0b\x32\x17.ateapi.SecurityContext\x12$\n\tresources\x18\t \x01(\x0b\x32\x11.ateapi.Resources\"=\n\x0fSecurityContext\x12*\n\x0c\x63\x61pabilities\x18\x01 \x01(\x0b\x32\x14.ateapi.Capabilities\")\n\x0c\x43\x61pabilities\x12\x0b\n\x03\x61\x64\x64\x18\x01 \x03(\t\x12\x0c\n\x04\x64rop\x18\x02 \x03(\t\"%\n\x06\x45nvVar\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"S\n\x0f\x43ontainerReadyz\x12\'\n\x08http_get\x18\x01 \x01(\x0b\x32\x15.ateapi.HTTPGetAction\x12\x17\n\x0ftimeout_seconds\x18\x02 \x01(\x05\"+\n\rHTTPGetAction\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0c\n\x04port\x18\x02 \x01(\x05\"\xec\x01\n\x06Volume\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x33\n\x0b\x64urable_dir\x18\x02 \x01(\x0b\x32\x1e.ateapi.DurableDirVolumeSource\x12@\n\x18\x65xternal_volume_template\x18\x03 \x01(\x0b\x32\x1e.ateapi.ExternalVolumeTemplate\x12\x33\n\x0bsystem_info\x18\x05 \x01(\x0b\x32\x1e.ateapi.SystemInfoVolumeSource\x12(\n\x05image\x18\x06 \x01(\x0b\x32\x19.ateapi.ImageVolumeSource\"&\n\x11ImageVolumeSource\x12\x11\n\treference\x18\x01 \x01(\t\"\x18\n\x16\x44urableDirVolumeSource\"F\n\x16\x45xternalVolumeTemplate\x12\x10\n\x08\x63\x61pacity\x18\x01 \x01(\t\x12\x1a\n\x12storage_class_name\x18\x02 \x01(\t\"L\n\x16SystemInfoVolumeSource\x12\x32\n\x0c\x64\x61ta_sources\x18\x01 \x03(\x0b\x32\x1c.ateapi.SystemInfoDataSource\"\x84\x01\n\x14SystemInfoDataSource\x12\x37\n\x0e\x61\x63tor_metadata\x18\x01 \x01(\x0b\x32\x1f.ateapi.ActorMetadataDataSource\x12\x33\n\x0ctrust_bundle\x18\x02 \x01(\x0b\x32\x1d.ateapi.TrustBundleDataSource\"C\n\x17\x41\x63torMetadataDataSource\x12(\n\x05items\x18\x01 \x03(\x0b\x32\x19.ateapi.ActorMetadataItem\"L\n\x11\x41\x63torMetadataItem\x12)\n\x05\x66ield\x18\x01 \x01(\x0e\x32\x1a.ateapi.ActorMetadataField\x12\x0c\n\x04path\x18\x02 \x01(\t\"3\n\x15TrustBundleDataSource\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\"/\n\x0bVolumeMount\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\nmount_path\x18\x02 \x01(\t\";\n\x15\x43reateAtespaceRequest\x12\"\n\x08\x61tespace\x18\x01 \x01(\x0b\x32\x10.ateapi.Atespace\"9\n\x12GetAtespaceRequest\x12#\n\x08\x61tespace\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"=\n\x14ListAtespacesRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\"U\n\x15ListAtespacesResponse\x12#\n\tatespaces\x18\x01 \x03(\x0b\x32\x10.ateapi.Atespace\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"<\n\x15\x44\x65leteAtespaceRequest\x12#\n\x08\x61tespace\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"K\n\x1a\x43reateActorTemplateRequest\x12-\n\x0e\x61\x63tor_template\x18\x01 \x01(\x0b\x32\x15.ateapi.ActorTemplate\"D\n\x17GetActorTemplateRequest\x12)\n\x0e\x61\x63tor_template\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"T\n\x19ListActorTemplatesRequest\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"e\n\x1aListActorTemplatesResponse\x12.\n\x0f\x61\x63tor_templates\x18\x01 \x03(\x0b\x32\x15.ateapi.ActorTemplate\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"G\n\x1a\x44\x65leteActorTemplateRequest\x12)\n\x0e\x61\x63tor_template\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"3\n\x0fGetActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"2\n\x12\x43reateActorRequest\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"2\n\x12UpdateActorRequest\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"7\n\x13SuspendActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"4\n\x14SuspendActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"5\n\x11PauseActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"2\n\x12PauseActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"D\n\x12ResumeActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x0c\n\x04\x62oot\x18\x02 \x01(\x08\"D\n\x13ResumeActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\x12\x0f\n\x07resumed\x18\x02 \x01(\x08\"I\n\x12\x44\x65leteActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x11\n\tany_state\x18\x02 \x01(\x08\"?\n\x1bGetActorEgressPolicyRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"o\n\x1e\x43reateActorEgressPolicyRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12+\n\regress_policy\x18\x02 \x01(\x0b\x32\x14.ateapi.EgressPolicy\"o\n\x1eUpdateActorEgressPolicyRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12+\n\regress_policy\x18\x02 \x01(\x0b\x32\x14.ateapi.EgressPolicy\"B\n\x1e\x44\x65leteActorEgressPolicyRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"/\n\rGetTagRequest\x12\x1e\n\x03tag\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"J\n\x0fListTagsRequest\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"F\n\x10ListTagsResponse\x12\x19\n\x04tags\x18\x01 \x03(\x0b\x32\x0b.ateapi.Tag\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\",\n\x10\x43reateTagRequest\x12\x18\n\x03tag\x18\x01 \x01(\x0b\x32\x0b.ateapi.Tag\",\n\x10UpdateTagRequest\x12\x18\n\x03tag\x18\x01 \x01(\x0b\x32\x0b.ateapi.Tag\"2\n\x10\x44\x65leteTagRequest\x12\x1e\n\x03tag\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"-\n\rDeleteOptions\x12\x0f\n\x07version\x18\x01 \x01(\x03\x12\x0b\n\x03uid\x18\x02 \x01(\t\"m\n!ListWorkerActorAssignmentsRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"q\n\"ListWorkerActorAssignmentsResponse\x12\x32\n\x11\x61\x63tor_assignments\x18\x01 \x03(\x0b\x32\x17.ateapi.ActorAssignment\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\";\n\x12ListWorkersRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\"O\n\x13ListWorkersResponse\x12\x1f\n\x07workers\x18\x01 \x03(\x0b\x32\x0e.ateapi.Worker\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"5\n\x10GetWorkerRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"5\n\x13\x43reateWorkerRequest\x12\x1e\n\x06worker\x18\x01 \x01(\x0b\x32\x0e.ateapi.Worker\"5\n\x13UpdateWorkerRequest\x12\x1e\n\x06worker\x18\x01 \x01(\x0b\x32\x0e.ateapi.Worker\"`\n\x13\x44\x65leteWorkerRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12&\n\x07options\x18\x02 \x01(\x0b\x32\x15.ateapi.DeleteOptions\"7\n\x12\x44rainWorkerRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"L\n\x11ListActorsRequest\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"L\n\x12ListActorsResponse\x12\x1d\n\x06\x61\x63tors\x18\x01 \x03(\x0b\x32\r.ateapi.Actor\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"\xc6\x02\n\x06Worker\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12\x18\n\x10worker_namespace\x18\x02 \x01(\t\x12\x13\n\x0bworker_pool\x18\x03 \x01(\t\x12\x12\n\nworker_pod\x18\x04 \x01(\t\x12\x16\n\x0eworker_pod_uid\x18\x05 \x01(\t\x12\x11\n\tnode_name\x18\x06 \x01(\t\x12\n\n\x02ip\x18\x07 \x01(\t\x12\x15\n\rsandbox_class\x18\x08 \x01(\t\x12*\n\x06labels\x18\t \x03(\x0b\x32\x1a.ateapi.Worker.LabelsEntry\x12$\n\x06status\x18\x0b \x01(\x0b\x32\x14.ateapi.WorkerStatus\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x89\x01\n\x0cWorkerStatus\x12\"\n\x05state\x18\x01 \x01(\x0e\x32\x13.ateapi.WorkerState\x12)\n\x08\x63\x61pacity\x18\x02 \x01(\x0b\x32\x17.ateapi.WorkerResources\x12*\n\tallocated\x18\x03 \x01(\x0b\x32\x17.ateapi.WorkerResources\"G\n\x0fWorkerResources\x12$\n\tresources\x18\x01 \x01(\x0b\x32\x11.ateapi.Resources\x12\x0e\n\x06\x61\x63tors\x18\x02 \x01(\x05\"\xc7\x01\n\x0f\x41\x63torAssignment\x12*\n\x08metadata\x18\x06 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12 \n\x05\x61\x63tor\x18\x02 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x11\n\tactor_uid\x18\x03 \x01(\t\x12-\n\x12\x61\x63tor_template_ref\x18\x04 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12$\n\tresources\x18\x05 \x01(\x0b\x32\x11.ateapi.Resources\"h\n\x18SetWorkerCapacityRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12)\n\x08\x63\x61pacity\x18\x02 \x01(\x0b\x32\x17.ateapi.WorkerResources\";\n\x19SetWorkerCapacityResponse\x12\x1e\n\x06worker\x18\x01 \x01(\x0b\x32\x0e.ateapi.Worker\"[\n\x0eMintJWTRequest\x12\x10\n\x08\x61udience\x18\x01 \x03(\t\x12\x10\n\x08\x61tespace\x18\x02 \x01(\t\x12\x12\n\nactor_name\x18\x03 \x01(\t\x12\x11\n\tactor_uid\x18\x04 \x01(\t\"$\n\x0fMintJWTResponse\x12\x11\n\tactor_jwt\x18\x01 \x01(\t\"\xa7\x01\n\x0fMintCertRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12#\n\x1b\x63\x65rtificate_signing_request\x18\x02 \x01(\x0c\x12\x1a\n\x12\x65xpected_actor_uid\x18\x03 \x01(\t\x12\x30\n\x07purpose\x18\x04 \x01(\x0e\x32\x1f.ateapi.ActorCertificatePurpose\".\n\x10MintCertResponse\x12\x1a\n\x12\x61\x63tor_certificates\x18\x01 \x03(\x0c*\x80\x01\n\x14SnapshotContentScope\x12&\n\"SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED\x10\x00\x12\x1f\n\x1bSNAPSHOT_CONTENT_SCOPE_FULL\x10\x01\x12\x1f\n\x1bSNAPSHOT_CONTENT_SCOPE_DATA\x10\x02*V\n\x08TagScope\x12\x19\n\x15TAG_SCOPE_UNSPECIFIED\x10\x00\x12\x16\n\x12TAG_SCOPE_ATESPACE\x10\x01\x12\x17\n\x13TAG_SCOPE_PUBLISHED\x10\x02*\xf7\x01\n\nActorState\x12\x1b\n\x17\x41\x43TOR_STATE_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41\x43TOR_STATE_RESUMING\x10\x01\x12\x17\n\x13\x41\x43TOR_STATE_RUNNING\x10\x02\x12\x1a\n\x16\x41\x43TOR_STATE_SUSPENDING\x10\x03\x12\x19\n\x15\x41\x43TOR_STATE_SUSPENDED\x10\x04\x12\x17\n\x13\x41\x43TOR_STATE_PAUSING\x10\x05\x12\x16\n\x12\x41\x43TOR_STATE_PAUSED\x10\x06\x12\x17\n\x13\x41\x43TOR_STATE_CRASHED\x10\x07\x12\x18\n\x14\x41\x43TOR_STATE_DELETING\x10\x08*b\n\x0cSandboxClass\x12\x1d\n\x19SANDBOX_CLASS_UNSPECIFIED\x10\x00\x12\x18\n\x14SANDBOX_CLASS_GVISOR\x10\x01\x12\x19\n\x15SANDBOX_CLASS_MICROVM\x10\x02*d\n\x0cResumeSource\x12\x1d\n\x19RESUME_SOURCE_UNSPECIFIED\x10\x00\x12\x1b\n\x17RESUME_SOURCE_COLD_BOOT\x10\x01\x12\x18\n\x14RESUME_SOURCE_GOLDEN\x10\x02*\x9a\x01\n\x12\x41\x63torMetadataField\x12$\n ACTOR_METADATA_FIELD_UNSPECIFIED\x10\x00\x12\x1d\n\x19\x41\x43TOR_METADATA_FIELD_NAME\x10\x01\x12!\n\x1d\x41\x43TOR_METADATA_FIELD_ATESPACE\x10\x02\x12\x1c\n\x18\x41\x43TOR_METADATA_FIELD_UID\x10\x03*_\n\x0bWorkerState\x12\x1c\n\x18WORKER_STATE_UNSPECIFIED\x10\x00\x12\x17\n\x13WORKER_STATE_ACTIVE\x10\x01\x12\x19\n\x15WORKER_STATE_DRAINING\x10\x02*k\n\x17\x41\x63torCertificatePurpose\x12)\n%ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED\x10\x00\x12%\n!ACTOR_CERTIFICATE_PURPOSE_ATUNNEL\x10\x01\x32\xf3\x11\n\x07\x43ontrol\x12\x34\n\x08GetActor\x12\x17.ateapi.GetActorRequest\x1a\r.ateapi.Actor\"\x00\x12:\n\x0b\x43reateActor\x12\x1a.ateapi.CreateActorRequest\x1a\r.ateapi.Actor\"\x00\x12:\n\x0bUpdateActor\x12\x1a.ateapi.UpdateActorRequest\x1a\r.ateapi.Actor\"\x00\x12K\n\x0cSuspendActor\x12\x1b.ateapi.SuspendActorRequest\x1a\x1c.ateapi.SuspendActorResponse\"\x00\x12\x45\n\nPauseActor\x12\x19.ateapi.PauseActorRequest\x1a\x1a.ateapi.PauseActorResponse\"\x00\x12H\n\x0bResumeActor\x12\x1a.ateapi.ResumeActorRequest\x1a\x1b.ateapi.ResumeActorResponse\"\x00\x12:\n\x0b\x44\x65leteActor\x12\x1a.ateapi.DeleteActorRequest\x1a\r.ateapi.Actor\"\x00\x12S\n\x14GetActorEgressPolicy\x12#.ateapi.GetActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n\x17\x43reateActorEgressPolicy\x12&.ateapi.CreateActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n\x17UpdateActorEgressPolicy\x12&.ateapi.UpdateActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n\x17\x44\x65leteActorEgressPolicy\x12&.ateapi.DeleteActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12\x34\n\tCreateTag\x12\x18.ateapi.CreateTagRequest\x1a\x0b.ateapi.Tag\"\x00\x12.\n\x06GetTag\x12\x15.ateapi.GetTagRequest\x1a\x0b.ateapi.Tag\"\x00\x12?\n\x08ListTags\x12\x17.ateapi.ListTagsRequest\x1a\x18.ateapi.ListTagsResponse\"\x00\x12\x34\n\tUpdateTag\x12\x18.ateapi.UpdateTagRequest\x1a\x0b.ateapi.Tag\"\x00\x12\x34\n\tDeleteTag\x12\x18.ateapi.DeleteTagRequest\x1a\x0b.ateapi.Tag\"\x00\x12H\n\x0bListWorkers\x12\x1a.ateapi.ListWorkersRequest\x1a\x1b.ateapi.ListWorkersResponse\"\x00\x12\x37\n\tGetWorker\x12\x18.ateapi.GetWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12=\n\x0c\x43reateWorker\x12\x1b.ateapi.CreateWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12=\n\x0cUpdateWorker\x12\x1b.ateapi.UpdateWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12=\n\x0c\x44\x65leteWorker\x12\x1b.ateapi.DeleteWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12;\n\x0b\x44rainWorker\x12\x1a.ateapi.DrainWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12u\n\x1aListWorkerActorAssignments\x12).ateapi.ListWorkerActorAssignmentsRequest\x1a*.ateapi.ListWorkerActorAssignmentsResponse\"\x00\x12\x45\n\nListActors\x12\x19.ateapi.ListActorsRequest\x1a\x1a.ateapi.ListActorsResponse\"\x00\x12\x43\n\x0e\x43reateAtespace\x12\x1d.ateapi.CreateAtespaceRequest\x1a\x10.ateapi.Atespace\"\x00\x12=\n\x0bGetAtespace\x12\x1a.ateapi.GetAtespaceRequest\x1a\x10.ateapi.Atespace\"\x00\x12N\n\rListAtespaces\x12\x1c.ateapi.ListAtespacesRequest\x1a\x1d.ateapi.ListAtespacesResponse\"\x00\x12\x43\n\x0e\x44\x65leteAtespace\x12\x1d.ateapi.DeleteAtespaceRequest\x1a\x10.ateapi.Atespace\"\x00\x12R\n\x13\x43reateActorTemplate\x12\".ateapi.CreateActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x00\x12L\n\x10GetActorTemplate\x12\x1f.ateapi.GetActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x00\x12]\n\x12ListActorTemplates\x12!.ateapi.ListActorTemplatesRequest\x1a\".ateapi.ListActorTemplatesResponse\"\x00\x12R\n\x13\x44\x65leteActorTemplate\x12\".ateapi.DeleteActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x00\x32\x8a\x01\n\rActorIdentity\x12:\n\x07MintJWT\x12\x16.ateapi.MintJWTRequest\x1a\x17.ateapi.MintJWTResponse\x12=\n\x08MintCert\x12\x17.ateapi.MintCertRequest\x1a\x18.ateapi.MintCertResponse2i\n\rWorkerService\x12X\n\x11SetWorkerCapacity\x12 .ateapi.SetWorkerCapacityRequest\x1a!.ateapi.SetWorkerCapacityResponseB9Z7github.com/agent-substrate/substrate/pkg/proto/ateapipbb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x61teapi.proto\x12\x06\x61teapi\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"]\n\x10\x45xternalSnapshot\x12\x14\n\x0csnapshot_uri\x18\x01 \x01(\t\x12\x33\n\rcontent_scope\x18\x02 \x01(\x0e\x32\x1c.ateapi.SnapshotContentScope\"\x86\x01\n\x11LocalSnapshotInfo\x12\x15\n\rsnapshot_name\x18\x01 \x01(\t\x12%\n\x1dnode_vms_with_local_snapshots\x18\x02 \x03(\t\x12\x33\n\rcontent_scope\x18\x03 \x01(\x0e\x32\x1c.ateapi.SnapshotContentScope\"w\n\x08Selector\x12\x37\n\x0cmatch_labels\x18\x01 \x03(\x0b\x32!.ateapi.Selector.MatchLabelsEntry\x1a\x32\n\x10MatchLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb2\x01\n\x10ResourceMetadata\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0b\n\x03uid\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\x03\x12/\n\x0b\x63reate_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0bupdate_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xdc\x02\n\x0e\x45xternalVolume\x12\x13\n\x0bvolume_name\x18\x01 \x01(\t\x12\x19\n\x11storage_volume_id\x18\x02 \x01(\t\x12\x13\n\x0bvolume_type\x18\x03 \x01(\t\x12-\n\x06status\x18\x04 \x01(\x0e\x32\x1d.ateapi.ExternalVolume.Status\x12\x41\n\x0evolume_context\x18\x05 \x03(\x0b\x32).ateapi.ExternalVolume.VolumeContextEntry\x1a\x34\n\x12VolumeContextEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"]\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n\x0eSTATUS_PENDING\x10\x01\x12\x12\n\x0eSTATUS_CREATED\x10\x02\x12\x13\n\x0fSTATUS_DELETING\x10\x03\"\xd5\x01\n\x05\x41\x63tor\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12)\n\x0e\x61\x63tor_template\x18\x04 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12)\n\x0fworker_selector\x18\x05 \x01(\x0b\x32\x10.ateapi.Selector\x12%\n\nsource_tag\x18\x06 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12#\n\x06status\x18\x07 \x01(\x0b\x32\x13.ateapi.ActorStatus\"]\n\x0c\x45gressPolicy\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12!\n\x05rules\x18\x02 \x03(\x0b\x32\x12.ateapi.EgressRule\"\x82\x01\n\nEgressRule\x12\'\n\thostnames\x18\x01 \x01(\x0b\x32\x14.ateapi.HostnameRule\x12&\n\tip_blocks\x18\x02 \x01(\x0b\x32\x13.ateapi.IPBlockRule\x12#\n\x03\x61ll\x18\x03 \x01(\x0b\x32\x16.google.protobuf.Empty\"L\n\x0cHostnameRule\x12\x10\n\x08patterns\x18\x01 \x03(\t\x12*\n\x07\x65\x66\x66\x65\x63ts\x18\x02 \x01(\x0b\x32\x19.ateapi.EgressRuleEffects\"\x1c\n\x0bIPBlockRule\x12\r\n\x05\x63idrs\x18\x01 \x03(\t\"U\n\x11\x45gressRuleEffects\x12@\n\x15inject_static_headers\x18\x01 \x03(\x0b\x32!.ateapi.CredentialHeaderInjection\"S\n\x19\x43redentialHeaderInjection\x12\x0e\n\x06header\x18\x01 \x01(\t\x12\x0e\n\x06prefix\x18\x02 \x01(\t\x12\x16\n\x0e\x63redential_uri\x18\x03 \x01(\t\"\xf1\x02\n\x0b\x41\x63torStatus\x12!\n\x05state\x18\x01 \x01(\x0e\x32\x12.ateapi.ActorState\x12\x33\n\x11worker_assignment\x18\x02 \x01(\x0b\x32\x18.ateapi.WorkerAssignment\x12!\n\x19in_progress_snapshot_name\x18\x03 \x01(\t\x12\x33\n\x11\x65xternal_snapshot\x18\x04 \x01(\x0b\x32\x18.ateapi.ExternalSnapshot\x12\x36\n\x13local_snapshot_info\x18\x05 \x01(\x0b\x32\x19.ateapi.LocalSnapshotInfo\x12-\n\ractor_volumes\x18\x07 \x03(\x0b\x32\x16.ateapi.ExternalVolume\x12\'\n\x1fin_progress_local_snapshot_name\x18\x08 \x01(\t\x12\"\n\x1a\x63urrent_actor_template_uid\x18\x0b \x01(\t\"\xa7\x01\n\x10WorkerAssignment\x12!\n\x06worker\x18\x06 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x18\n\x10worker_namespace\x18\x01 \x01(\t\x12\x13\n\x0bworker_pool\x18\x02 \x01(\t\x12\x12\n\nworker_pod\x18\x03 \x01(\t\x12\x16\n\x0eworker_pod_uid\x18\x04 \x01(\t\x12\x15\n\rworker_pod_ip\x18\x05 \x01(\t\"\x8f\x01\n\tTagStatus\x12*\n\x08snapshot\x18\x01 \x01(\x0b\x32\x18.ateapi.ExternalSnapshot\x12\x1a\n\x12\x61\x63tor_template_uid\x18\x02 \x01(\t\x12 \n\x18in_progress_snapshot_uri\x18\x03 \x01(\t\x12\x18\n\x10source_actor_uid\x18\x04 \x01(\t\"\x9e\x01\n\x03Tag\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12!\n\x06status\x18\x02 \x01(\x0b\x32\x11.ateapi.TagStatus\x12\x1f\n\x05scope\x18\x03 \x01(\x0e\x32\x10.ateapi.TagScope\x12\'\n\x0csource_actor\x18\x04 \x01(\x0b\x32\x11.ateapi.ObjectRef\"6\n\x08\x41tespace\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\"+\n\tObjectRef\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xe3\x02\n\rActorTemplate\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12)\n\x0fworker_selector\x18\x02 \x01(\x0b\x32\x10.ateapi.Selector\x12%\n\ncontainers\x18\x03 \x03(\x0b\x32\x11.ateapi.Container\x12\x1f\n\x07volumes\x18\x04 \x03(\x0b\x32\x0e.ateapi.Volume\x12\x31\n\x10snapshots_config\x18\x05 \x01(\x0b\x32\x17.ateapi.SnapshotsConfig\x12-\n\x0esandbox_config\x18\x06 \x01(\x0b\x32\x15.ateapi.SandboxConfig\x12$\n\tresources\x18\x07 \x01(\x0b\x32\x11.ateapi.Resources\x12+\n\x06status\x18\x08 \x01(\x0b\x32\x1b.ateapi.ActorTemplateStatus\"+\n\tResources\x12\x1e\n\x06limits\x18\x01 \x03(\x0b\x32\x0e.ateapi.Limits\"(\n\x06Limits\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08quantity\x18\x02 \x01(\t\"\x9d\x01\n\x14GoldenSnapshotStatus\x12\x31\n\x0fgolden_snapshot\x18\x01 \x01(\x0b\x32\x18.ateapi.ExternalSnapshot\x12;\n\x17take_golden_snapshot_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x15\n\rerror_message\x18\x03 \x01(\t\"S\n\x13\x41\x63torTemplateStatus\x12<\n\x16golden_snapshot_status\x18\x01 \x01(\x0b\x32\x1c.ateapi.GoldenSnapshotStatus\"Q\n\rSandboxConfig\x12+\n\rsandbox_class\x18\x01 \x01(\x0e\x32\x14.ateapi.SandboxClass\x12\x13\n\x0b\x63onfig_name\x18\x02 \x01(\t\"\xb7\x01\n\x0fSnapshotsConfig\x12.\n\x08on_pause\x18\x01 \x01(\x0e\x32\x1c.ateapi.SnapshotContentScope\x12/\n\ton_commit\x18\x02 \x01(\x0e\x32\x1c.ateapi.SnapshotContentScope\x12)\n\ton_resume\x18\x03 \x01(\x0b\x32\x16.ateapi.OnResumeConfig\x12\x18\n\x10storage_location\x18\x04 \x01(\t\"9\n\x0eOnResumeConfig\x12\'\n\tfrom_data\x18\x01 \x01(\x0e\x32\x14.ateapi.ResumeSource\"\x92\x02\n\tContainer\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05image\x18\x02 \x01(\t\x12\x0f\n\x07\x63ommand\x18\x03 \x03(\t\x12\x0c\n\x04\x61rgs\x18\x04 \x03(\t\x12\x1b\n\x03\x65nv\x18\x05 \x03(\x0b\x32\x0e.ateapi.EnvVar\x12\'\n\x06readyz\x18\x06 \x01(\x0b\x32\x17.ateapi.ContainerReadyz\x12*\n\rvolume_mounts\x18\x07 \x03(\x0b\x32\x13.ateapi.VolumeMount\x12\x31\n\x10security_context\x18\x08 \x01(\x0b\x32\x17.ateapi.SecurityContext\x12$\n\tresources\x18\t \x01(\x0b\x32\x11.ateapi.Resources\"=\n\x0fSecurityContext\x12*\n\x0c\x63\x61pabilities\x18\x01 \x01(\x0b\x32\x14.ateapi.Capabilities\")\n\x0c\x43\x61pabilities\x12\x0b\n\x03\x61\x64\x64\x18\x01 \x03(\t\x12\x0c\n\x04\x64rop\x18\x02 \x03(\t\"%\n\x06\x45nvVar\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"S\n\x0f\x43ontainerReadyz\x12\'\n\x08http_get\x18\x01 \x01(\x0b\x32\x15.ateapi.HTTPGetAction\x12\x17\n\x0ftimeout_seconds\x18\x02 \x01(\x05\"+\n\rHTTPGetAction\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0c\n\x04port\x18\x02 \x01(\x05\"\xec\x01\n\x06Volume\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x33\n\x0b\x64urable_dir\x18\x02 \x01(\x0b\x32\x1e.ateapi.DurableDirVolumeSource\x12@\n\x18\x65xternal_volume_template\x18\x03 \x01(\x0b\x32\x1e.ateapi.ExternalVolumeTemplate\x12\x33\n\x0bsystem_info\x18\x05 \x01(\x0b\x32\x1e.ateapi.SystemInfoVolumeSource\x12(\n\x05image\x18\x06 \x01(\x0b\x32\x19.ateapi.ImageVolumeSource\"&\n\x11ImageVolumeSource\x12\x11\n\treference\x18\x01 \x01(\t\"\x18\n\x16\x44urableDirVolumeSource\"F\n\x16\x45xternalVolumeTemplate\x12\x10\n\x08\x63\x61pacity\x18\x01 \x01(\t\x12\x1a\n\x12storage_class_name\x18\x02 \x01(\t\"L\n\x16SystemInfoVolumeSource\x12\x32\n\x0c\x64\x61ta_sources\x18\x01 \x03(\x0b\x32\x1c.ateapi.SystemInfoDataSource\"\x84\x01\n\x14SystemInfoDataSource\x12\x37\n\x0e\x61\x63tor_metadata\x18\x01 \x01(\x0b\x32\x1f.ateapi.ActorMetadataDataSource\x12\x33\n\x0ctrust_bundle\x18\x02 \x01(\x0b\x32\x1d.ateapi.TrustBundleDataSource\"C\n\x17\x41\x63torMetadataDataSource\x12(\n\x05items\x18\x01 \x03(\x0b\x32\x19.ateapi.ActorMetadataItem\"L\n\x11\x41\x63torMetadataItem\x12)\n\x05\x66ield\x18\x01 \x01(\x0e\x32\x1a.ateapi.ActorMetadataField\x12\x0c\n\x04path\x18\x02 \x01(\t\"3\n\x15TrustBundleDataSource\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\"/\n\x0bVolumeMount\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\nmount_path\x18\x02 \x01(\t\";\n\x15\x43reateAtespaceRequest\x12\"\n\x08\x61tespace\x18\x01 \x01(\x0b\x32\x10.ateapi.Atespace\"9\n\x12GetAtespaceRequest\x12#\n\x08\x61tespace\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"=\n\x14ListAtespacesRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\"U\n\x15ListAtespacesResponse\x12#\n\tatespaces\x18\x01 \x03(\x0b\x32\x10.ateapi.Atespace\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"<\n\x15\x44\x65leteAtespaceRequest\x12#\n\x08\x61tespace\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"K\n\x1a\x43reateActorTemplateRequest\x12-\n\x0e\x61\x63tor_template\x18\x01 \x01(\x0b\x32\x15.ateapi.ActorTemplate\"D\n\x17GetActorTemplateRequest\x12)\n\x0e\x61\x63tor_template\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"T\n\x19ListActorTemplatesRequest\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"e\n\x1aListActorTemplatesResponse\x12.\n\x0f\x61\x63tor_templates\x18\x01 \x03(\x0b\x32\x15.ateapi.ActorTemplate\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"G\n\x1a\x44\x65leteActorTemplateRequest\x12)\n\x0e\x61\x63tor_template\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"3\n\x0fGetActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"2\n\x12\x43reateActorRequest\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"2\n\x12UpdateActorRequest\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"7\n\x13SuspendActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"4\n\x14SuspendActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"5\n\x11PauseActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"2\n\x12PauseActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\"D\n\x12ResumeActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x0c\n\x04\x62oot\x18\x02 \x01(\x08\"D\n\x13ResumeActorResponse\x12\x1c\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\r.ateapi.Actor\x12\x0f\n\x07resumed\x18\x02 \x01(\x08\"I\n\x12\x44\x65leteActorRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x11\n\tany_state\x18\x02 \x01(\x08\"?\n\x1bGetActorEgressPolicyRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"o\n\x1e\x43reateActorEgressPolicyRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12+\n\regress_policy\x18\x02 \x01(\x0b\x32\x14.ateapi.EgressPolicy\"o\n\x1eUpdateActorEgressPolicyRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12+\n\regress_policy\x18\x02 \x01(\x0b\x32\x14.ateapi.EgressPolicy\"B\n\x1e\x44\x65leteActorEgressPolicyRequest\x12 \n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"/\n\rGetTagRequest\x12\x1e\n\x03tag\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"\\\n\x13MintActorJWTRequest\x12 \n\x05\x61\x63tor\x18\x05 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x11\n\tactor_uid\x18\x07 \x01(\t\x12\x10\n\x08\x61udience\x18\x01 \x03(\t\")\n\x14MintActorJWTResponse\x12\x11\n\tactor_jwt\x18\x01 \x01(\t\"\xa9\x01\n\x1bMintActorCertificateRequest\x12 \n\x05\x61\x63tor\x18\x06 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x11\n\tactor_uid\x18\x07 \x01(\t\x12#\n\x1b\x63\x65rtificate_signing_request\x18\x02 \x01(\x0c\x12\x30\n\x07purpose\x18\x04 \x01(\x0e\x32\x1f.ateapi.ActorCertificatePurpose\":\n\x1cMintActorCertificateResponse\x12\x1a\n\x12\x61\x63tor_certificates\x18\x01 \x03(\x0c\"D\n\x17GetActorSnapshotRequest\x12)\n\x0e\x61\x63tor_snapshot\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"K\n\x1aGetActorSnapshotTagRequest\x12-\n\x12\x61\x63tor_snapshot_tag\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"J\n\x0fListTagsRequest\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"F\n\x10ListTagsResponse\x12\x19\n\x04tags\x18\x01 \x03(\x0b\x32\x0b.ateapi.Tag\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\",\n\x10\x43reateTagRequest\x12\x18\n\x03tag\x18\x01 \x01(\x0b\x32\x0b.ateapi.Tag\",\n\x10UpdateTagRequest\x12\x18\n\x03tag\x18\x01 \x01(\x0b\x32\x0b.ateapi.Tag\"2\n\x10\x44\x65leteTagRequest\x12\x1e\n\x03tag\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"-\n\rDeleteOptions\x12\x0f\n\x07version\x18\x01 \x01(\x03\x12\x0b\n\x03uid\x18\x02 \x01(\t\"m\n!ListWorkerActorAssignmentsRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"q\n\"ListWorkerActorAssignmentsResponse\x12\x32\n\x11\x61\x63tor_assignments\x18\x01 \x03(\x0b\x32\x17.ateapi.ActorAssignment\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\";\n\x12ListWorkersRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\"O\n\x13ListWorkersResponse\x12\x1f\n\x07workers\x18\x01 \x03(\x0b\x32\x0e.ateapi.Worker\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"5\n\x10GetWorkerRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"5\n\x13\x43reateWorkerRequest\x12\x1e\n\x06worker\x18\x01 \x01(\x0b\x32\x0e.ateapi.Worker\"5\n\x13UpdateWorkerRequest\x12\x1e\n\x06worker\x18\x01 \x01(\x0b\x32\x0e.ateapi.Worker\"`\n\x13\x44\x65leteWorkerRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12&\n\x07options\x18\x02 \x01(\x0b\x32\x15.ateapi.DeleteOptions\"7\n\x12\x44rainWorkerRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\"L\n\x11ListActorsRequest\x12\x10\n\x08\x61tespace\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"L\n\x12ListActorsResponse\x12\x1d\n\x06\x61\x63tors\x18\x01 \x03(\x0b\x32\r.ateapi.Actor\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"\xc6\x02\n\x06Worker\x12*\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12\x18\n\x10worker_namespace\x18\x02 \x01(\t\x12\x13\n\x0bworker_pool\x18\x03 \x01(\t\x12\x12\n\nworker_pod\x18\x04 \x01(\t\x12\x16\n\x0eworker_pod_uid\x18\x05 \x01(\t\x12\x11\n\tnode_name\x18\x06 \x01(\t\x12\n\n\x02ip\x18\x07 \x01(\t\x12\x15\n\rsandbox_class\x18\x08 \x01(\t\x12*\n\x06labels\x18\t \x03(\x0b\x32\x1a.ateapi.Worker.LabelsEntry\x12$\n\x06status\x18\x0b \x01(\x0b\x32\x14.ateapi.WorkerStatus\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x89\x01\n\x0cWorkerStatus\x12\"\n\x05state\x18\x01 \x01(\x0e\x32\x13.ateapi.WorkerState\x12)\n\x08\x63\x61pacity\x18\x02 \x01(\x0b\x32\x17.ateapi.WorkerResources\x12*\n\tallocated\x18\x03 \x01(\x0b\x32\x17.ateapi.WorkerResources\"G\n\x0fWorkerResources\x12$\n\tresources\x18\x01 \x01(\x0b\x32\x11.ateapi.Resources\x12\x0e\n\x06\x61\x63tors\x18\x02 \x01(\x05\"\xc7\x01\n\x0f\x41\x63torAssignment\x12*\n\x08metadata\x18\x06 \x01(\x0b\x32\x18.ateapi.ResourceMetadata\x12 \n\x05\x61\x63tor\x18\x02 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12\x11\n\tactor_uid\x18\x03 \x01(\t\x12-\n\x12\x61\x63tor_template_ref\x18\x04 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12$\n\tresources\x18\x05 \x01(\x0b\x32\x11.ateapi.Resources\"h\n\x18SetWorkerCapacityRequest\x12!\n\x06worker\x18\x01 \x01(\x0b\x32\x11.ateapi.ObjectRef\x12)\n\x08\x63\x61pacity\x18\x02 \x01(\x0b\x32\x17.ateapi.WorkerResources\";\n\x19SetWorkerCapacityResponse\x12\x1e\n\x06worker\x18\x01 \x01(\x0b\x32\x0e.ateapi.Worker*\x80\x01\n\x14SnapshotContentScope\x12&\n\"SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED\x10\x00\x12\x1f\n\x1bSNAPSHOT_CONTENT_SCOPE_FULL\x10\x01\x12\x1f\n\x1bSNAPSHOT_CONTENT_SCOPE_DATA\x10\x02*V\n\x08TagScope\x12\x19\n\x15TAG_SCOPE_UNSPECIFIED\x10\x00\x12\x16\n\x12TAG_SCOPE_ATESPACE\x10\x01\x12\x17\n\x13TAG_SCOPE_PUBLISHED\x10\x02*\xf7\x01\n\nActorState\x12\x1b\n\x17\x41\x43TOR_STATE_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41\x43TOR_STATE_RESUMING\x10\x01\x12\x17\n\x13\x41\x43TOR_STATE_RUNNING\x10\x02\x12\x1a\n\x16\x41\x43TOR_STATE_SUSPENDING\x10\x03\x12\x19\n\x15\x41\x43TOR_STATE_SUSPENDED\x10\x04\x12\x17\n\x13\x41\x43TOR_STATE_PAUSING\x10\x05\x12\x16\n\x12\x41\x43TOR_STATE_PAUSED\x10\x06\x12\x17\n\x13\x41\x43TOR_STATE_CRASHED\x10\x07\x12\x18\n\x14\x41\x43TOR_STATE_DELETING\x10\x08*b\n\x0cSandboxClass\x12\x1d\n\x19SANDBOX_CLASS_UNSPECIFIED\x10\x00\x12\x18\n\x14SANDBOX_CLASS_GVISOR\x10\x01\x12\x19\n\x15SANDBOX_CLASS_MICROVM\x10\x02*d\n\x0cResumeSource\x12\x1d\n\x19RESUME_SOURCE_UNSPECIFIED\x10\x00\x12\x1b\n\x17RESUME_SOURCE_COLD_BOOT\x10\x01\x12\x18\n\x14RESUME_SOURCE_GOLDEN\x10\x02*\x9a\x01\n\x12\x41\x63torMetadataField\x12$\n ACTOR_METADATA_FIELD_UNSPECIFIED\x10\x00\x12\x1d\n\x19\x41\x43TOR_METADATA_FIELD_NAME\x10\x01\x12!\n\x1d\x41\x43TOR_METADATA_FIELD_ATESPACE\x10\x02\x12\x1c\n\x18\x41\x43TOR_METADATA_FIELD_UID\x10\x03*k\n\x17\x41\x63torCertificatePurpose\x12)\n%ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED\x10\x00\x12%\n!ACTOR_CERTIFICATE_PURPOSE_ATUNNEL\x10\x01*_\n\x0bWorkerState\x12\x1c\n\x18WORKER_STATE_UNSPECIFIED\x10\x00\x12\x17\n\x13WORKER_STATE_ACTIVE\x10\x01\x12\x19\n\x15WORKER_STATE_DRAINING\x10\x02\x32\xa5\x13\n\x07\x43ontrol\x12\x34\n\x08GetActor\x12\x17.ateapi.GetActorRequest\x1a\r.ateapi.Actor\"\x00\x12:\n\x0b\x43reateActor\x12\x1a.ateapi.CreateActorRequest\x1a\r.ateapi.Actor\"\x00\x12:\n\x0bUpdateActor\x12\x1a.ateapi.UpdateActorRequest\x1a\r.ateapi.Actor\"\x00\x12K\n\x0cSuspendActor\x12\x1b.ateapi.SuspendActorRequest\x1a\x1c.ateapi.SuspendActorResponse\"\x00\x12\x45\n\nPauseActor\x12\x19.ateapi.PauseActorRequest\x1a\x1a.ateapi.PauseActorResponse\"\x00\x12H\n\x0bResumeActor\x12\x1a.ateapi.ResumeActorRequest\x1a\x1b.ateapi.ResumeActorResponse\"\x00\x12:\n\x0b\x44\x65leteActor\x12\x1a.ateapi.DeleteActorRequest\x1a\r.ateapi.Actor\"\x00\x12S\n\x14GetActorEgressPolicy\x12#.ateapi.GetActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n\x17\x43reateActorEgressPolicy\x12&.ateapi.CreateActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n\x17UpdateActorEgressPolicy\x12&.ateapi.UpdateActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n\x17\x44\x65leteActorEgressPolicy\x12&.ateapi.DeleteActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12K\n\x0cMintActorJWT\x12\x1b.ateapi.MintActorJWTRequest\x1a\x1c.ateapi.MintActorJWTResponse\"\x00\x12\x63\n\x14MintActorCertificate\x12#.ateapi.MintActorCertificateRequest\x1a$.ateapi.MintActorCertificateResponse\"\x00\x12\x34\n\tCreateTag\x12\x18.ateapi.CreateTagRequest\x1a\x0b.ateapi.Tag\"\x00\x12.\n\x06GetTag\x12\x15.ateapi.GetTagRequest\x1a\x0b.ateapi.Tag\"\x00\x12?\n\x08ListTags\x12\x17.ateapi.ListTagsRequest\x1a\x18.ateapi.ListTagsResponse\"\x00\x12\x34\n\tUpdateTag\x12\x18.ateapi.UpdateTagRequest\x1a\x0b.ateapi.Tag\"\x00\x12\x34\n\tDeleteTag\x12\x18.ateapi.DeleteTagRequest\x1a\x0b.ateapi.Tag\"\x00\x12H\n\x0bListWorkers\x12\x1a.ateapi.ListWorkersRequest\x1a\x1b.ateapi.ListWorkersResponse\"\x00\x12\x37\n\tGetWorker\x12\x18.ateapi.GetWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12=\n\x0c\x43reateWorker\x12\x1b.ateapi.CreateWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12=\n\x0cUpdateWorker\x12\x1b.ateapi.UpdateWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12=\n\x0c\x44\x65leteWorker\x12\x1b.ateapi.DeleteWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12;\n\x0b\x44rainWorker\x12\x1a.ateapi.DrainWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12u\n\x1aListWorkerActorAssignments\x12).ateapi.ListWorkerActorAssignmentsRequest\x1a*.ateapi.ListWorkerActorAssignmentsResponse\"\x00\x12\x45\n\nListActors\x12\x19.ateapi.ListActorsRequest\x1a\x1a.ateapi.ListActorsResponse\"\x00\x12\x43\n\x0e\x43reateAtespace\x12\x1d.ateapi.CreateAtespaceRequest\x1a\x10.ateapi.Atespace\"\x00\x12=\n\x0bGetAtespace\x12\x1a.ateapi.GetAtespaceRequest\x1a\x10.ateapi.Atespace\"\x00\x12N\n\rListAtespaces\x12\x1c.ateapi.ListAtespacesRequest\x1a\x1d.ateapi.ListAtespacesResponse\"\x00\x12\x43\n\x0e\x44\x65leteAtespace\x12\x1d.ateapi.DeleteAtespaceRequest\x1a\x10.ateapi.Atespace\"\x00\x12R\n\x13\x43reateActorTemplate\x12\".ateapi.CreateActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x00\x12L\n\x10GetActorTemplate\x12\x1f.ateapi.GetActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x00\x12]\n\x12ListActorTemplates\x12!.ateapi.ListActorTemplatesRequest\x1a\".ateapi.ListActorTemplatesResponse\"\x00\x12R\n\x13\x44\x65leteActorTemplate\x12\".ateapi.DeleteActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x00\x32i\n\rWorkerService\x12X\n\x11SetWorkerCapacity\x12 .ateapi.SetWorkerCapacityRequest\x1a!.ateapi.SetWorkerCapacityResponseB9Z7github.com/agent-substrate/substrate/pkg/proto/ateapipbb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -54,22 +54,22 @@ _globals['_EXTERNALVOLUME_VOLUMECONTEXTENTRY']._serialized_options = b'8\001' _globals['_WORKER_LABELSENTRY']._loaded_options = None _globals['_WORKER_LABELSENTRY']._serialized_options = b'8\001' - _globals['_SNAPSHOTCONTENTSCOPE']._serialized_start=9237 - _globals['_SNAPSHOTCONTENTSCOPE']._serialized_end=9365 - _globals['_TAGSCOPE']._serialized_start=9367 - _globals['_TAGSCOPE']._serialized_end=9453 - _globals['_ACTORSTATE']._serialized_start=9456 - _globals['_ACTORSTATE']._serialized_end=9703 - _globals['_SANDBOXCLASS']._serialized_start=9705 - _globals['_SANDBOXCLASS']._serialized_end=9803 - _globals['_RESUMESOURCE']._serialized_start=9805 - _globals['_RESUMESOURCE']._serialized_end=9905 - _globals['_ACTORMETADATAFIELD']._serialized_start=9908 - _globals['_ACTORMETADATAFIELD']._serialized_end=10062 - _globals['_WORKERSTATE']._serialized_start=10064 - _globals['_WORKERSTATE']._serialized_end=10159 - _globals['_ACTORCERTIFICATEPURPOSE']._serialized_start=10161 - _globals['_ACTORCERTIFICATEPURPOSE']._serialized_end=10268 + _globals['_SNAPSHOTCONTENTSCOPE']._serialized_start=9404 + _globals['_SNAPSHOTCONTENTSCOPE']._serialized_end=9532 + _globals['_TAGSCOPE']._serialized_start=9534 + _globals['_TAGSCOPE']._serialized_end=9620 + _globals['_ACTORSTATE']._serialized_start=9623 + _globals['_ACTORSTATE']._serialized_end=9870 + _globals['_SANDBOXCLASS']._serialized_start=9872 + _globals['_SANDBOXCLASS']._serialized_end=9970 + _globals['_RESUMESOURCE']._serialized_start=9972 + _globals['_RESUMESOURCE']._serialized_end=10072 + _globals['_ACTORMETADATAFIELD']._serialized_start=10075 + _globals['_ACTORMETADATAFIELD']._serialized_end=10229 + _globals['_ACTORCERTIFICATEPURPOSE']._serialized_start=10231 + _globals['_ACTORCERTIFICATEPURPOSE']._serialized_end=10338 + _globals['_WORKERSTATE']._serialized_start=10340 + _globals['_WORKERSTATE']._serialized_end=10435 _globals['_EXTERNALSNAPSHOT']._serialized_start=86 _globals['_EXTERNALSNAPSHOT']._serialized_end=179 _globals['_LOCALSNAPSHOTINFO']._serialized_start=182 @@ -210,66 +210,68 @@ _globals['_DELETEACTOREGRESSPOLICYREQUEST']._serialized_end=6742 _globals['_GETTAGREQUEST']._serialized_start=6744 _globals['_GETTAGREQUEST']._serialized_end=6791 - _globals['_LISTTAGSREQUEST']._serialized_start=6793 - _globals['_LISTTAGSREQUEST']._serialized_end=6867 - _globals['_LISTTAGSRESPONSE']._serialized_start=6869 - _globals['_LISTTAGSRESPONSE']._serialized_end=6939 - _globals['_CREATETAGREQUEST']._serialized_start=6941 - _globals['_CREATETAGREQUEST']._serialized_end=6985 - _globals['_UPDATETAGREQUEST']._serialized_start=6987 - _globals['_UPDATETAGREQUEST']._serialized_end=7031 - _globals['_DELETETAGREQUEST']._serialized_start=7033 - _globals['_DELETETAGREQUEST']._serialized_end=7083 - _globals['_DELETEOPTIONS']._serialized_start=7085 - _globals['_DELETEOPTIONS']._serialized_end=7130 - _globals['_LISTWORKERACTORASSIGNMENTSREQUEST']._serialized_start=7132 - _globals['_LISTWORKERACTORASSIGNMENTSREQUEST']._serialized_end=7241 - _globals['_LISTWORKERACTORASSIGNMENTSRESPONSE']._serialized_start=7243 - _globals['_LISTWORKERACTORASSIGNMENTSRESPONSE']._serialized_end=7356 - _globals['_LISTWORKERSREQUEST']._serialized_start=7358 - _globals['_LISTWORKERSREQUEST']._serialized_end=7417 - _globals['_LISTWORKERSRESPONSE']._serialized_start=7419 - _globals['_LISTWORKERSRESPONSE']._serialized_end=7498 - _globals['_GETWORKERREQUEST']._serialized_start=7500 - _globals['_GETWORKERREQUEST']._serialized_end=7553 - _globals['_CREATEWORKERREQUEST']._serialized_start=7555 - _globals['_CREATEWORKERREQUEST']._serialized_end=7608 - _globals['_UPDATEWORKERREQUEST']._serialized_start=7610 - _globals['_UPDATEWORKERREQUEST']._serialized_end=7663 - _globals['_DELETEWORKERREQUEST']._serialized_start=7665 - _globals['_DELETEWORKERREQUEST']._serialized_end=7761 - _globals['_DRAINWORKERREQUEST']._serialized_start=7763 - _globals['_DRAINWORKERREQUEST']._serialized_end=7818 - _globals['_LISTACTORSREQUEST']._serialized_start=7820 - _globals['_LISTACTORSREQUEST']._serialized_end=7896 - _globals['_LISTACTORSRESPONSE']._serialized_start=7898 - _globals['_LISTACTORSRESPONSE']._serialized_end=7974 - _globals['_WORKER']._serialized_start=7977 - _globals['_WORKER']._serialized_end=8303 - _globals['_WORKER_LABELSENTRY']._serialized_start=8258 - _globals['_WORKER_LABELSENTRY']._serialized_end=8303 - _globals['_WORKERSTATUS']._serialized_start=8306 - _globals['_WORKERSTATUS']._serialized_end=8443 - _globals['_WORKERRESOURCES']._serialized_start=8445 - _globals['_WORKERRESOURCES']._serialized_end=8516 - _globals['_ACTORASSIGNMENT']._serialized_start=8519 - _globals['_ACTORASSIGNMENT']._serialized_end=8718 - _globals['_SETWORKERCAPACITYREQUEST']._serialized_start=8720 - _globals['_SETWORKERCAPACITYREQUEST']._serialized_end=8824 - _globals['_SETWORKERCAPACITYRESPONSE']._serialized_start=8826 - _globals['_SETWORKERCAPACITYRESPONSE']._serialized_end=8885 - _globals['_MINTJWTREQUEST']._serialized_start=8887 - _globals['_MINTJWTREQUEST']._serialized_end=8978 - _globals['_MINTJWTRESPONSE']._serialized_start=8980 - _globals['_MINTJWTRESPONSE']._serialized_end=9016 - _globals['_MINTCERTREQUEST']._serialized_start=9019 - _globals['_MINTCERTREQUEST']._serialized_end=9186 - _globals['_MINTCERTRESPONSE']._serialized_start=9188 - _globals['_MINTCERTRESPONSE']._serialized_end=9234 - _globals['_CONTROL']._serialized_start=10271 - _globals['_CONTROL']._serialized_end=12562 - _globals['_ACTORIDENTITY']._serialized_start=12565 - _globals['_ACTORIDENTITY']._serialized_end=12703 - _globals['_WORKERSERVICE']._serialized_start=12705 - _globals['_WORKERSERVICE']._serialized_end=12810 + _globals['_MINTACTORJWTREQUEST']._serialized_start=6793 + _globals['_MINTACTORJWTREQUEST']._serialized_end=6885 + _globals['_MINTACTORJWTRESPONSE']._serialized_start=6887 + _globals['_MINTACTORJWTRESPONSE']._serialized_end=6928 + _globals['_MINTACTORCERTIFICATEREQUEST']._serialized_start=6931 + _globals['_MINTACTORCERTIFICATEREQUEST']._serialized_end=7100 + _globals['_MINTACTORCERTIFICATERESPONSE']._serialized_start=7102 + _globals['_MINTACTORCERTIFICATERESPONSE']._serialized_end=7160 + _globals['_GETACTORSNAPSHOTREQUEST']._serialized_start=7162 + _globals['_GETACTORSNAPSHOTREQUEST']._serialized_end=7230 + _globals['_GETACTORSNAPSHOTTAGREQUEST']._serialized_start=7232 + _globals['_GETACTORSNAPSHOTTAGREQUEST']._serialized_end=7307 + _globals['_LISTTAGSREQUEST']._serialized_start=7309 + _globals['_LISTTAGSREQUEST']._serialized_end=7383 + _globals['_LISTTAGSRESPONSE']._serialized_start=7385 + _globals['_LISTTAGSRESPONSE']._serialized_end=7455 + _globals['_CREATETAGREQUEST']._serialized_start=7457 + _globals['_CREATETAGREQUEST']._serialized_end=7501 + _globals['_UPDATETAGREQUEST']._serialized_start=7503 + _globals['_UPDATETAGREQUEST']._serialized_end=7547 + _globals['_DELETETAGREQUEST']._serialized_start=7549 + _globals['_DELETETAGREQUEST']._serialized_end=7599 + _globals['_DELETEOPTIONS']._serialized_start=7601 + _globals['_DELETEOPTIONS']._serialized_end=7646 + _globals['_LISTWORKERACTORASSIGNMENTSREQUEST']._serialized_start=7648 + _globals['_LISTWORKERACTORASSIGNMENTSREQUEST']._serialized_end=7757 + _globals['_LISTWORKERACTORASSIGNMENTSRESPONSE']._serialized_start=7759 + _globals['_LISTWORKERACTORASSIGNMENTSRESPONSE']._serialized_end=7872 + _globals['_LISTWORKERSREQUEST']._serialized_start=7874 + _globals['_LISTWORKERSREQUEST']._serialized_end=7933 + _globals['_LISTWORKERSRESPONSE']._serialized_start=7935 + _globals['_LISTWORKERSRESPONSE']._serialized_end=8014 + _globals['_GETWORKERREQUEST']._serialized_start=8016 + _globals['_GETWORKERREQUEST']._serialized_end=8069 + _globals['_CREATEWORKERREQUEST']._serialized_start=8071 + _globals['_CREATEWORKERREQUEST']._serialized_end=8124 + _globals['_UPDATEWORKERREQUEST']._serialized_start=8126 + _globals['_UPDATEWORKERREQUEST']._serialized_end=8179 + _globals['_DELETEWORKERREQUEST']._serialized_start=8181 + _globals['_DELETEWORKERREQUEST']._serialized_end=8277 + _globals['_DRAINWORKERREQUEST']._serialized_start=8279 + _globals['_DRAINWORKERREQUEST']._serialized_end=8334 + _globals['_LISTACTORSREQUEST']._serialized_start=8336 + _globals['_LISTACTORSREQUEST']._serialized_end=8412 + _globals['_LISTACTORSRESPONSE']._serialized_start=8414 + _globals['_LISTACTORSRESPONSE']._serialized_end=8490 + _globals['_WORKER']._serialized_start=8493 + _globals['_WORKER']._serialized_end=8819 + _globals['_WORKER_LABELSENTRY']._serialized_start=8774 + _globals['_WORKER_LABELSENTRY']._serialized_end=8819 + _globals['_WORKERSTATUS']._serialized_start=8822 + _globals['_WORKERSTATUS']._serialized_end=8959 + _globals['_WORKERRESOURCES']._serialized_start=8961 + _globals['_WORKERRESOURCES']._serialized_end=9032 + _globals['_ACTORASSIGNMENT']._serialized_start=9035 + _globals['_ACTORASSIGNMENT']._serialized_end=9234 + _globals['_SETWORKERCAPACITYREQUEST']._serialized_start=9236 + _globals['_SETWORKERCAPACITYREQUEST']._serialized_end=9340 + _globals['_SETWORKERCAPACITYRESPONSE']._serialized_start=9342 + _globals['_SETWORKERCAPACITYRESPONSE']._serialized_end=9401 + _globals['_CONTROL']._serialized_start=10438 + _globals['_CONTROL']._serialized_end=12907 + _globals['_WORKERSERVICE']._serialized_start=12909 + _globals['_WORKERSERVICE']._serialized_end=13014 # @@protoc_insertion_point(module_scope) diff --git a/benchmarking/locust/common/ateapi_pb2_grpc.py b/benchmarking/locust/common/ateapi_pb2_grpc.py index 069ac3e2eb..1e4f80ea64 100644 --- a/benchmarking/locust/common/ateapi_pb2_grpc.py +++ b/benchmarking/locust/common/ateapi_pb2_grpc.py @@ -104,6 +104,16 @@ def __init__(self, channel): request_serializer=ateapi__pb2.DeleteActorEgressPolicyRequest.SerializeToString, response_deserializer=ateapi__pb2.EgressPolicy.FromString, _registered_method=True) + self.MintActorJWT = channel.unary_unary( + '/ateapi.Control/MintActorJWT', + request_serializer=ateapi__pb2.MintActorJWTRequest.SerializeToString, + response_deserializer=ateapi__pb2.MintActorJWTResponse.FromString, + _registered_method=True) + self.MintActorCertificate = channel.unary_unary( + '/ateapi.Control/MintActorCertificate', + request_serializer=ateapi__pb2.MintActorCertificateRequest.SerializeToString, + response_deserializer=ateapi__pb2.MintActorCertificateResponse.FromString, + _registered_method=True) self.CreateTag = channel.unary_unary( '/ateapi.Control/CreateTag', request_serializer=ateapi__pb2.CreateTagRequest.SerializeToString, @@ -294,6 +304,28 @@ def DeleteActorEgressPolicy(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def MintActorJWT(self, request, context): + """Create a Substrate-issued JWT asserting the actor identity. + + * Called by the egress gateway when actor JWT injection is configured for outbound requests. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def MintActorCertificate(self, request, context): + """Create a Substrate-issued SPIFFE certificate asserting the actor identity. + + * Called by atelet to provision an atunnel with a certificate for + communication with the egress gateway. TODO(ahmedtd): Migrate this use + case to a distinct certificate to prevent actor/atunnel confusion. + * Called by the egress gateway when actor client certificate injection is + configured for outbound requests. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def CreateTag(self, request, context): """Tag the external snapshot a suspended Actor holds. The tag gets its own copy of that snapshot, so suspending or deleting the Actor afterwards @@ -505,6 +537,16 @@ def add_ControlServicer_to_server(servicer, server): request_deserializer=ateapi__pb2.DeleteActorEgressPolicyRequest.FromString, response_serializer=ateapi__pb2.EgressPolicy.SerializeToString, ), + 'MintActorJWT': grpc.unary_unary_rpc_method_handler( + servicer.MintActorJWT, + request_deserializer=ateapi__pb2.MintActorJWTRequest.FromString, + response_serializer=ateapi__pb2.MintActorJWTResponse.SerializeToString, + ), + 'MintActorCertificate': grpc.unary_unary_rpc_method_handler( + servicer.MintActorCertificate, + request_deserializer=ateapi__pb2.MintActorCertificateRequest.FromString, + response_serializer=ateapi__pb2.MintActorCertificateResponse.SerializeToString, + ), 'CreateTag': grpc.unary_unary_rpc_method_handler( servicer.CreateTag, request_deserializer=ateapi__pb2.CreateTagRequest.FromString, @@ -919,6 +961,60 @@ def DeleteActorEgressPolicy(request, metadata, _registered_method=True) + @staticmethod + def MintActorJWT(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/ateapi.Control/MintActorJWT', + ateapi__pb2.MintActorJWTRequest.SerializeToString, + ateapi__pb2.MintActorJWTResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def MintActorCertificate(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/ateapi.Control/MintActorCertificate', + ateapi__pb2.MintActorCertificateRequest.SerializeToString, + ateapi__pb2.MintActorCertificateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def CreateTag(request, target, @@ -1487,154 +1583,6 @@ def DeleteActorTemplate(request, _registered_method=True) -class ActorIdentityStub: - """ActorIdentity allows substrate workloads to exchange their - infrastructure-level credentials (k8s service account token, etc.) for a - substrate actor-level credential. A given substrate actor might migrate - between many different physical workers over the course of its lifecycle, - whereas the actor credential's identity will be stable for the life of the - actor. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.MintJWT = channel.unary_unary( - '/ateapi.ActorIdentity/MintJWT', - request_serializer=ateapi__pb2.MintJWTRequest.SerializeToString, - response_deserializer=ateapi__pb2.MintJWTResponse.FromString, - _registered_method=True) - self.MintCert = channel.unary_unary( - '/ateapi.ActorIdentity/MintCert', - request_serializer=ateapi__pb2.MintCertRequest.SerializeToString, - response_deserializer=ateapi__pb2.MintCertResponse.FromString, - _registered_method=True) - - -class ActorIdentityServicer: - """ActorIdentity allows substrate workloads to exchange their - infrastructure-level credentials (k8s service account token, etc.) for a - substrate actor-level credential. A given substrate actor might migrate - between many different physical workers over the course of its lifecycle, - whereas the actor credential's identity will be stable for the life of the - actor. - """ - - def MintJWT(self, request, context): - """Request an Actor Identity JWT. - - To call this RPC, you must be authenticated as the Kubernetes Pod that is - currently running the requested actor. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def MintCert(self, request, context): - """Request an Actor Identity Certificate for an actor. - - Actors do not call this RPC themselves. The atelet hosting the actor calls - it on the actor's behalf, authenticating with its own client certificate - rather than a bearer token. - - Authorization is decided on that client certificate and the worker - identity attested by atelet. Ateapi verifies that the worker is assigned to - the actor and that the actor points back to that exact worker before signing. - - The certificate in the response is the actor's identity, not the atelet's. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ActorIdentityServicer_to_server(servicer, server): - rpc_method_handlers = { - 'MintJWT': grpc.unary_unary_rpc_method_handler( - servicer.MintJWT, - request_deserializer=ateapi__pb2.MintJWTRequest.FromString, - response_serializer=ateapi__pb2.MintJWTResponse.SerializeToString, - ), - 'MintCert': grpc.unary_unary_rpc_method_handler( - servicer.MintCert, - request_deserializer=ateapi__pb2.MintCertRequest.FromString, - response_serializer=ateapi__pb2.MintCertResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'ateapi.ActorIdentity', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('ateapi.ActorIdentity', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class ActorIdentity: - """ActorIdentity allows substrate workloads to exchange their - infrastructure-level credentials (k8s service account token, etc.) for a - substrate actor-level credential. A given substrate actor might migrate - between many different physical workers over the course of its lifecycle, - whereas the actor credential's identity will be stable for the life of the - actor. - """ - - @staticmethod - def MintJWT(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/ateapi.ActorIdentity/MintJWT', - ateapi__pb2.MintJWTRequest.SerializeToString, - ateapi__pb2.MintJWTResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def MintCert(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/ateapi.ActorIdentity/MintCert', - ateapi__pb2.MintCertRequest.SerializeToString, - ateapi__pb2.MintCertResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - class WorkerServiceStub: """WorkerService is how a Worker tells the control plane about itself. It is separate from Control because the two have different callers and different diff --git a/cmd/ateapi/internal/actoridentity/actoridentity.go b/cmd/ateapi/internal/actoridentity/actoridentity.go deleted file mode 100644 index a27e420f4a..0000000000 --- a/cmd/ateapi/internal/actoridentity/actoridentity.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2026 Google LLC -// -// 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 actoridentity - -import ( - "context" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "errors" - "fmt" - "log/slog" - "net/url" - "path" - "time" - - "github.com/agent-substrate/substrate/cmd/ateapi/internal/ateletauth" - "github.com/agent-substrate/substrate/cmd/ateapi/internal/controlapi" - "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" - "github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache" - "github.com/agent-substrate/substrate/internal/actoridjwt" - "github.com/agent-substrate/substrate/internal/localca" - "github.com/agent-substrate/substrate/internal/localjwtauthority" - "github.com/agent-substrate/substrate/internal/principal" - "github.com/agent-substrate/substrate/internal/resources" - "github.com/agent-substrate/substrate/internal/substratex509" - "github.com/agent-substrate/substrate/pkg/proto/ateapipb" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "k8s.io/apimachinery/pkg/api/operation" - "k8s.io/apimachinery/pkg/util/validation/field" -) - -// Server implements ateapipb.ActorIdentityServer -type Server struct { - ateapipb.UnimplementedActorIdentityServer - - // TODO(identity): Issuer is probably logically a property of the JWT - // signing pool. - actorIdentityJWTIssuer string - - actorIDJWTPool localjwtauthority.Pool - actorIDCAPool localca.Pool - - // store is the actor database. MintCert consults it to confirm the caller - // is entitled to the actor it is asking for a credential for. - store store.Interface - workers *workercache.Cache -} - -var _ ateapipb.ActorIdentityServer = (*Server)(nil) - -func New(actorIdentityJWTIssuer string, actorIDJWTPool localjwtauthority.Pool, actorIDCAPool localca.Pool, store store.Interface, workers *workercache.Cache) *Server { - return &Server{ - actorIdentityJWTIssuer: actorIdentityJWTIssuer, - actorIDJWTPool: actorIDJWTPool, - actorIDCAPool: actorIDCAPool, - store: store, - workers: workers, - } -} - -const actorCertificateLifetime = time.Hour - -func (s *Server) MintJWT(ctx context.Context, req *ateapipb.MintJWTRequest) (*ateapipb.MintJWTResponse, error) { - caller, ok := principal.FromContext(ctx) - if !ok || caller.Kind != principal.KindJWT { - return nil, status.Errorf(codes.Unauthenticated, "JWT authentication is required") - } - if caller.Issuer != s.actorIdentityJWTIssuer { - return nil, status.Errorf(codes.PermissionDenied, "caller is not permitted to mint actor JWTs") - } - - if errs := validateMintJWTRequest(ctx, req); len(errs) > 0 { - return nil, status.Error(codes.InvalidArgument, errs.ToAggregate().Error()) - } - - // TODO: Cross-check the verified caller and requested actor against the actor database. - - // We only issue tokens with audience bindings. - if len(req.GetAudience()) == 0 { - return nil, fmt.Errorf("at least one audience must be requested") - } - - actorClaims := &actoridjwt.Claims{ - // TODO: This is currently API but it has to be a globally unique, oidc-compliant and accsible DNS name - Issuer: "https://api.ate-system.svc", - // TODO: this format is very likely going to change. - Subject: fmt.Sprintf("atespaces:%s:actors:%s", req.GetAtespace(), req.GetActorName()), - Audiences: req.GetAudience(), - Expiration: time.Now().Add(15 * time.Minute), - NotBefore: time.Now().Add(-5 * time.Minute), - IssuedAt: time.Now(), - JTI: rand.Text(), - - Substrate: actoridjwt.SubstrateClaims{ - Atespace: req.GetAtespace(), - ActorName: req.GetActorName(), - ActorUID: req.GetActorUid(), - }, - } - - actorJWT, err := s.actorIDJWTPool.SignJWT(actorClaims) - if err != nil { - return nil, fmt.Errorf("while signing actor JWT: %w", err) - } - - return &ateapipb.MintJWTResponse{ - ActorJwt: actorJWT, - }, nil -} - -func (s *Server) MintCert(ctx context.Context, req *ateapipb.MintCertRequest) (*ateapipb.MintCertResponse, error) { - caller, err := ateletauth.Authenticate(ctx) - if err != nil { - return nil, err - } - if errs := validateMintCertRequest(ctx, req); len(errs) > 0 { - return nil, status.Error(codes.InvalidArgument, errs.ToAggregate().Error()) - } - // Validation bounds purpose to the enum's range; which purposes this - // server actually supports is a policy decision that stays here. - if req.GetPurpose() != ateapipb.ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_ATUNNEL { - return nil, status.Error(codes.InvalidArgument, "unsupported actor certificate purpose") - } - actor, actorRef, err := s.authorizeActor(ctx, caller, req) - if err != nil { - return nil, err - } - atespace, actorName := actorRef.Atespace, actorRef.Name - - // expected_actor_uid picked which actor to mint for (see authorizeActor); - // re-checking it here fails closed if the request crossed an assignment - // change. - actorUID := actor.GetMetadata().GetUid() - if actorUID == "" { - slog.ErrorContext(ctx, "MintCert: actor has no UID", slog.Any("actor", actorRef)) - return nil, status.Errorf(codes.Internal, "actor has no UID") - } - if req.GetExpectedActorUid() != actorUID { - slog.WarnContext(ctx, "MintCert refused: expected actor UID does not match the placed actor", - slog.Any("actor", actorRef), slog.String("expectedActorUID", req.GetExpectedActorUid())) - return nil, status.Error(codes.FailedPrecondition, "worker assignment changed while minting actor certificate") - } - - // Parse the CSR - csr, err := x509.ParseCertificateRequest(req.GetCertificateSigningRequest()) - if err != nil { - slog.ErrorContext(ctx, "Failed to parse CSR", slog.Any("err", err)) - return nil, status.Errorf(codes.Internal, "Failed to parse CSR") - } - if err := csr.CheckSignature(); err != nil { - slog.ErrorContext(ctx, "Failed to verify CSR signature", slog.Any("err", err)) - return nil, status.Errorf(codes.Internal, "Failed to verify CSR signature") - } - - spiffeURI := &url.URL{ - Scheme: "spiffe", - Host: "substrate-actor.local", - Path: path.Join("atespace", atespace, "actor", actorName), - } - template := &x509.Certificate{ - URIs: []*url.URL{spiffeURI}, - NotBefore: time.Now().Add(-5 * time.Minute), - NotAfter: time.Now().Add(actorCertificateLifetime), - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - BasicConstraintsValid: true, - IsCA: false, - Issuer: pkix.Name{ - CommonName: "api.ate-system.svc.cluster.local", - }, - } - - if err := substratex509.AddActorIdentityToCertificate(&substratex509.ActorIdentity{ - Atespace: atespace, - ActorName: actorName, - ActorUid: actorUID, - Purpose: substratex509.ActorIdentityPurposeAtunnel, - }, template); err != nil { - slog.ErrorContext(ctx, "Failed to add ActorIdentity extension", slog.Any("err", err)) - return nil, status.Errorf(codes.Internal, "Failed to build certificate") - } - - // Sign and return the actor cert. - chain, err := s.actorIDCAPool.CreateCertificate(template, csr.PublicKey) - if err != nil { - slog.ErrorContext(ctx, "Failed to sign certificate", slog.Any("err", err)) - return nil, status.Errorf(codes.Internal, "Failed to sign certificate") - } - - return &ateapipb.MintCertResponse{ - ActorCertificates: chain, - }, nil -} - -func validateMintJWTRequest(ctx context.Context, req *ateapipb.MintJWTRequest) field.ErrorList { - // Call the generated validation. - op := operation.Operation{Type: operation.Create} - return controlapi.Validate_MintJWTRequest(ctx, op, nil, req, nil) -} - -func validateMintCertRequest(ctx context.Context, req *ateapipb.MintCertRequest) field.ErrorList { - // Call the generated validation. - op := operation.Operation{Type: operation.Create} - return controlapi.Validate_MintCertRequest(ctx, op, nil, req, nil) -} - -// authorizeActor resolves the actor the request names among those the worker is -// hosting and verifies the two still point at one another. Requester-supplied -// identity never participates in the decision. -// -// The worker is resolved from cache first (hot path), but cache misses and -// denials fall back to the authoritative store to handle watch-delivery lag -// right after ResumeActor. -func (s *Server) authorizeActor(ctx context.Context, caller *ateletauth.Caller, req *ateapipb.MintCertRequest) (*ateapipb.Actor, resources.ActorRef, error) { - reason := "worker not found" - worker, err := s.workers.Worker(req.GetWorker().GetName()) - if err != nil && !errors.Is(err, store.ErrNotFound) { - slog.ErrorContext(ctx, "ActorIdentity: failed to read worker", slog.Any("err", err)) - return nil, resources.ActorRef{}, status.Error(codes.Internal, "failed to look up worker") - } - if err == nil { - actor, actorRef, mismatchReason, err := s.authorizeWithWorker(ctx, worker, caller, req) - if err == nil { - return actor, actorRef, nil - } - if !errors.Is(err, errAssignmentMismatch) { - return nil, resources.ActorRef{}, err // e.g. actor lookup failed - } - reason = mismatchReason - } - - // Read-through: re-check the authoritative worker from the store on a - // cache miss or assignment mismatch. Only fresh data may authorize, and - // only fresh data may deny. - fresh, ferr := s.store.GetWorker(ctx, req.GetWorker().GetName()) - if ferr != nil { - if !errors.Is(ferr, store.ErrNotFound) { - slog.ErrorContext(ctx, "ActorIdentity: read-through worker lookup failed", slog.Any("err", ferr)) - } - return nil, resources.ActorRef{}, s.denyMint(ctx, caller, req, reason) // the cached verdict stands - } - - actor, actorRef, retryReason, retryErr := s.authorizeWithWorker(ctx, fresh, caller, req) - if retryErr != nil { - if errors.Is(retryErr, errAssignmentMismatch) { - return nil, resources.ActorRef{}, s.denyMint(ctx, caller, req, retryReason) - } - return nil, resources.ActorRef{}, retryErr - } - - slog.InfoContext(ctx, "ActorIdentity: authorized via store read-through; worker cache was stale", - slog.String("worker", req.GetWorker().GetName())) - return actor, actorRef, nil -} - -// denyMint logs the internal reason and returns a uniform PermissionDenied. -// Denials are deliberately indistinguishable from each other: a caller that -// is not entitled to a worker should not learn its assignment. -func (s *Server) denyMint(ctx context.Context, caller *ateletauth.Caller, req *ateapipb.MintCertRequest, reason string, args ...any) error { - slog.WarnContext(ctx, "ActorIdentity denied: "+reason, - append([]any{slog.String("worker", req.GetWorker().GetName()), slog.String("callerPod", caller.PodName), slog.String("callerNode", caller.NodeName)}, args...)...) - return status.Error(codes.PermissionDenied, "caller is not permitted to mint credentials for this actor") -} - -var errAssignmentMismatch = errors.New("assignment mismatch") - -// authorizeWithWorker returns errAssignmentMismatch and a reason string if the authorization failed -// due to an assignment mismatch, indicating the caller may want to refetch the worker and retry. -func (s *Server) authorizeWithWorker(ctx context.Context, worker *ateapipb.Worker, caller *ateletauth.Caller, req *ateapipb.MintCertRequest) (*ateapipb.Actor, resources.ActorRef, string, error) { - if worker.GetNodeName() != caller.NodeName { - return nil, resources.ActorRef{}, "worker is hosted on a different node", errAssignmentMismatch - } - - assigned, err := s.assignmentToMintFor(ctx, worker.GetMetadata().GetName(), req.GetExpectedActorUid()) - if errors.Is(err, store.ErrNotFound) { - return nil, resources.ActorRef{}, "worker is not hosting the requested actor", errAssignmentMismatch - } - if err != nil { - slog.ErrorContext(ctx, "ActorIdentity: failed to read worker assignment", slog.Any("err", err)) - return nil, resources.ActorRef{}, "", status.Error(codes.Internal, "failed to look up worker assignment") - } - actorRef := resources.ActorRefFromObjectRef(assigned.GetActor()) - if actorRef == (resources.ActorRef{}) { - return nil, resources.ActorRef{}, "worker assignment names no actor", errAssignmentMismatch - } - - actor, err := s.store.GetActor(ctx, actorRef) - if err != nil { - if errors.Is(err, store.ErrNotFound) { - return nil, resources.ActorRef{}, "assigned actor not found", errAssignmentMismatch - } - slog.ErrorContext(ctx, "ActorIdentity: failed to read actor", slog.Any("actor", actorRef), slog.Any("err", err)) - return nil, resources.ActorRef{}, "", status.Error(codes.Internal, "failed to look up actor") - } - - // Refuse credential minting if the actor is being deleted. Under force deletion, - // an actor enters ACTOR_STATE_DELETING while its worker assignment is still active. - if actor.GetStatus().GetState() == ateapipb.ActorState_ACTOR_STATE_DELETING { - slog.WarnContext(ctx, "ActorIdentity refused: actor is being deleted", slog.Any("actor", actorRef)) - return nil, resources.ActorRef{}, "", status.Error(codes.FailedPrecondition, "actor is being deleted") - } - - assignment := actor.GetStatus().GetWorkerAssignment() - if assignment == nil { - slog.ErrorContext(ctx, "ActorIdentity: running actor has no worker assignment", slog.Any("actor", actorRef)) - return nil, resources.ActorRef{}, "", status.Error(codes.FailedPrecondition, "actor has no worker assigned") - } - if assigned.GetActorUid() != actor.GetMetadata().GetUid() { - return nil, resources.ActorRef{}, "worker is no longer assigned to this actor incarnation", errAssignmentMismatch - } - if assignment.GetWorker().GetName() != worker.GetMetadata().GetName() { - return nil, resources.ActorRef{}, "actor no longer points to the requesting worker", errAssignmentMismatch - } - return actor, actorRef, "", nil -} - -// assignmentToMintFor picks which of the worker's actors to mint for, or -// ErrNotFound when it hosts none. expected_actor_uid selects from what ateapi -// records the worker as hosting; it does not assert. -// -// Falling back to another of the worker's assignments keeps a bad binding -// (PermissionDenied) apart from a stale expectation (retryable). Reads go to -// the store: the binding was committed moments ago and the watch has not -// delivered it. Only one other assignment is needed to tell the two apart, so -// the fallback reads a single row rather than the Worker's whole occupancy. -func (s *Server) assignmentToMintFor(ctx context.Context, workerName, actorUID string) (*ateapipb.ActorAssignment, error) { - assigned, err := s.store.GetWorkerAssignment(ctx, workerName, actorUID) - if err == nil { - return assigned, nil - } - if !errors.Is(err, store.ErrNotFound) { - return nil, err - } - page, err := s.store.ListWorkerAssignments(ctx, workerName, store.ListOptions{PageSize: 1}) - if err != nil { - return nil, err - } - if len(page.Items) == 0 { - return nil, store.ErrNotFound - } - return page.Items[0], nil -} diff --git a/cmd/ateapi/internal/actoridentity/actoridentity_test.go b/cmd/ateapi/internal/actoridentity/actoridentity_test.go deleted file mode 100644 index 5df75e0c25..0000000000 --- a/cmd/ateapi/internal/actoridentity/actoridentity_test.go +++ /dev/null @@ -1,1103 +0,0 @@ -// Copyright 2026 Google LLC -// -// 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 actoridentity - -import ( - "context" - "crypto/ed25519" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "fmt" - "path" - "strings" - "testing" - "time" - - "github.com/agent-substrate/substrate/cmd/ateapi/internal/ateletauth" - "github.com/agent-substrate/substrate/cmd/ateapi/internal/ateletauth/ateletauthtest" - - "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" - "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest" - "github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache" - "github.com/agent-substrate/substrate/internal/localca" - "github.com/agent-substrate/substrate/internal/localjwtauthority" - "github.com/agent-substrate/substrate/internal/principal" - "github.com/agent-substrate/substrate/internal/resources" - "github.com/agent-substrate/substrate/internal/substratex509" - "github.com/agent-substrate/substrate/pkg/proto/ateapipb" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "k8s.io/apimachinery/pkg/util/validation/field" -) - -func assertValidateErr(t *testing.T, got field.ErrorList, want field.ErrorList) { - t.Helper() - field.ErrorMatcher{}.ByType().ByField().ByOrigin().Test(t, want, got) -} - -const ( - testAtespace = "team-alpha" - testActorName = "counter-1" - testPodNS = "ate-workers" - testWorkerPod = "worker-abc" - // testWorkerName is the seeded worker's resource name, and so the name - // MintCert requests reference it by. It is deliberately not equal to - // testWorkerPodUID: MintCert must resolve the worker by name alone. - testWorkerName = "5b1e0c7a-8d34-4f62-b0a9-1e7c4d29f350" - testWorkerPodUID = "e2c40f8b-71d9-4a35-8c6e-b04f9d1a7263" - testPool = "pool-1" - testNode = "node-a" - testOtherNode = "node-b" -) - -// newTestCert builds a self-signed leaf carrying the given SPIFFE URI path -// (skipped when empty) and, when podIdentity is non-nil, a PodIdentity -// extension. -// -// The certificate is created and then re-parsed on purpose: -// AddPodIdentityToCertificate writes to ExtraExtensions, but -// PodIdentityFromCertificate reads Extensions, which only x509.ParseCertificate -// populates. Self-signing is sufficient because the code under test reads an -// already transport-verified peer certificate and never re-validates the chain -// itself. - -// newTestServer returns a Server backed by st, with a freshly generated actor -// CA pool written to a temp file. -func newTestServer(t *testing.T, st store.Interface) *Server { - t.Helper() - - certificateAuthority, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour) - if err != nil { - t.Fatalf("generate CA: %v", err) - } - certificateAuthorityPool := &localca.ConcretePool{ - CAs: []*localca.CA{certificateAuthority}, - ActiveForSigning: "test-actor-ca", - } - - jwtAuthority, err := localjwtauthority.GenerateECDSAP256Authority("1") - if err != nil { - t.Fatalf("while generating JWT authority: %v", err) - } - jwtAuthorityPool := &localjwtauthority.ConcretePool{ - Authorities: []*localjwtauthority.Authority{jwtAuthority}, - ActiveForSigning: "1", - } - - var workers *workercache.Cache - if st != nil { - workers = workercache.New(st, time.Hour) - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - if err := workers.Start(ctx); err != nil { - t.Fatalf("start worker cache: %v", err) - } - } - return New("issuer", jwtAuthorityPool, certificateAuthorityPool, st, workers) -} - -// staleWatchStore wraps a store with a WatchWorkers that never delivers, -// freezing any workercache built over it at its seed-time state — the unit -// analog of the watch's delivery latency. -type staleWatchStore struct{ store.Interface } - -func (s staleWatchStore) WatchWorkers(context.Context) (*store.WorkerWatch, error) { - return store.NewWorkerWatch(make(chan store.WorkerEvent), func() {}), nil -} - -// TestMintCertReadsThroughStaleWorkerCache pins the authorization -// read-through: an atelet minting immediately after ResumeActor committed the -// worker's assignment must be authorized from the store even though this -// replica's cache has not yet seen the assignment. The control case keeps the -// store unassigned too and must still deny — only fresh data may authorize, -// and only fresh data may deny. -func TestMintCertReadsThroughStaleWorkerCache(t *testing.T) { - for name, assignInStore := range map[string]bool{ - "assignment committed but not yet in cache: authorized via read-through": true, - "unassigned in cache and store: denial stands": false, - } { - t.Run(name, func(t *testing.T) { - ctx := context.Background() - st, cleanup := storetest.SetupTestStore(t) - defer cleanup() - - // Phase 1: worker exists, unassigned; the cache seeds this view and - // (via the inert watch) never learns anything newer. - seedActor(t, ctx, st, actorFixture{state: ateapipb.ActorState_ACTOR_STATE_RUNNING, workerNode: testNode, unassigned: true}) - workers := workercache.New(staleWatchStore{st}, time.Hour) - cacheCtx, cancel := context.WithCancel(ctx) - t.Cleanup(cancel) - if err := workers.Start(cacheCtx); err != nil { - t.Fatalf("start worker cache: %v", err) - } - - actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName}) - if err != nil { - t.Fatalf("read seeded actor: %v", err) - } - if assignInStore { - // Phase 2: commit the assignment to the store only, as - // AssignWorker does (possibly on another replica). - bindActor(t, ctx, st, testWorkerName, &ateapipb.ActorAssignment{ - Actor: (resources.ActorRef{Atespace: testAtespace, Name: testActorName}).ToObjectRef(), - ActorUid: actor.GetMetadata().GetUid(), - }) - } - - srv := newTestServerWithCache(t, st, workers) - resp, err := srv.MintCert(ateletauthtest.ContextWith(ateletauthtest.CertOn(t, testNode)), mintCertRequest(t, actor.GetMetadata().GetUid())) - - wantCode := codes.PermissionDenied - if assignInStore { - wantCode = codes.OK - } - if got := status.Code(err); got != wantCode { - t.Fatalf("MintCert() code = %v (err = %v), want %v", got, err, wantCode) - } - if assignInStore && len(resp.GetActorCertificates()) == 0 { - t.Fatal("MintCert() returned no certificates") - } - }) - } -} - -// The multi-actor case of the same lag: the cached worker is not unassigned but -// hosting somebody else, so the requested actor's absence looks like an answer -// rather than a miss. -func TestMintCertReadsThroughForAnActorTheCacheHasNotSeenYet(t *testing.T) { - ctx := context.Background() - st, cleanup := storetest.SetupTestStore(t) - defer cleanup() - - // The cache seeds with the worker hosting only the first actor, and (via - // the inert watch) never learns of the second. - seedActor(t, ctx, st, actorFixture{state: ateapipb.ActorState_ACTOR_STATE_RUNNING, workerNode: testNode}) - workers := workercache.New(staleWatchStore{st}, time.Hour) - cacheCtx, cancel := context.WithCancel(ctx) - t.Cleanup(cancel) - if err := workers.Start(cacheCtx); err != nil { - t.Fatalf("start worker cache: %v", err) - } - - const secondActorName = "counter-2" - second, err := st.CreateActor(ctx, &ateapipb.Actor{ - Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: secondActorName}, - Status: &ateapipb.ActorStatus{ - State: ateapipb.ActorState_ACTOR_STATE_RUNNING, - WorkerAssignment: &ateapipb.WorkerAssignment{ - Worker: &ateapipb.ObjectRef{Name: testWorkerName}, - WorkerNamespace: testPodNS, - WorkerPool: testPool, - WorkerPod: testWorkerPod, - WorkerPodUid: testWorkerPodUID, - }, - }, - ActorTemplate: &ateapipb.ObjectRef{Atespace: "ate-demo", Name: "counter"}, - }) - if err != nil { - t.Fatalf("seed second actor: %v", err) - } - - // Bind it to the worker in the store only, as AssignWorker does. - bindActor(t, ctx, st, testWorkerName, &ateapipb.ActorAssignment{ - Actor: (resources.ActorRef{Atespace: testAtespace, Name: secondActorName}).ToObjectRef(), - ActorUid: second.GetMetadata().GetUid(), - }) - - srv := newTestServerWithCache(t, st, workers) - resp, err := srv.MintCert(ateletauthtest.ContextWith(ateletauthtest.CertOn(t, testNode)), mintCertRequest(t, second.GetMetadata().GetUid())) - if err != nil { - t.Fatalf("MintCert() for an actor the cache has not seen: %v", err) - } - if len(resp.GetActorCertificates()) == 0 { - t.Fatal("MintCert() returned no certificates") - } - - leaf, err := x509.ParseCertificate(resp.GetActorCertificates()[0]) - if err != nil { - t.Fatalf("parse minted certificate: %v", err) - } - identity, err := substratex509.ActorIdentityFromCertificate(leaf) - if err != nil { - t.Fatalf("ActorIdentityFromCertificate: %v", err) - } - if identity == nil || identity.ActorName != secondActorName { - t.Errorf("minted for %+v, want actor %q", identity, secondActorName) - } -} - -// TestMintCertReadsThroughWorkerCacheMiss pins the read-through for a worker -// the cache has never seen: a worker registered moments before assignment may -// be committed to the store (possibly by another replica) before this -// replica's cache has received the worker row at all. Absence from the cache -// is stale data and must not deny by itself; absence from the store must. -func TestMintCertReadsThroughWorkerCacheMiss(t *testing.T) { - for name, workerInStore := range map[string]bool{ - "worker assigned in store but not yet in cache: authorized via read-through": true, - "worker in neither cache nor store: denial stands": false, - } { - t.Run(name, func(t *testing.T) { - ctx := context.Background() - st, cleanup := storetest.SetupTestStore(t) - defer cleanup() - - // Phase 1: only the actor exists; the cache seeds with no workers - // and (via the inert watch) never learns of any. - seedActor(t, ctx, st, actorFixture{state: ateapipb.ActorState_ACTOR_STATE_RUNNING, workerNode: testNode, noWorker: true}) - workers := workercache.New(staleWatchStore{st}, time.Hour) - cacheCtx, cancel := context.WithCancel(ctx) - t.Cleanup(cancel) - if err := workers.Start(cacheCtx); err != nil { - t.Fatalf("start worker cache: %v", err) - } - - actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName}) - if err != nil { - t.Fatalf("read seeded actor: %v", err) - } - if workerInStore { - // Phase 2: register and assign the worker in the store only, - // after the cache stopped listening. - if _, err := st.CreateWorker(ctx, &ateapipb.Worker{ - Metadata: &ateapipb.ResourceMetadata{Name: testWorkerName}, - WorkerNamespace: testPodNS, - WorkerPool: testPool, - WorkerPod: testWorkerPod, - WorkerPodUid: testWorkerPodUID, - NodeName: testNode, - Status: &ateapipb.WorkerStatus{State: ateapipb.WorkerState_WORKER_STATE_ACTIVE}, - }); err != nil { - t.Fatalf("register worker in store: %v", err) - } - bindActor(t, ctx, st, testWorkerName, &ateapipb.ActorAssignment{ - Actor: (resources.ActorRef{Atespace: testAtespace, Name: testActorName}).ToObjectRef(), - ActorUid: actor.GetMetadata().GetUid(), - }) - } - - srv := newTestServerWithCache(t, st, workers) - resp, err := srv.MintCert(ateletauthtest.ContextWith(ateletauthtest.CertOn(t, testNode)), mintCertRequest(t, actor.GetMetadata().GetUid())) - - wantCode := codes.PermissionDenied - if workerInStore { - wantCode = codes.OK - } - if got := status.Code(err); got != wantCode { - t.Fatalf("MintCert() code = %v (err = %v), want %v", got, err, wantCode) - } - if workerInStore && len(resp.GetActorCertificates()) == 0 { - t.Fatal("MintCert() returned no certificates") - } - }) - } -} - -// newTestServerWithCache is newTestServer with a caller-controlled worker -// cache (e.g. one frozen at a stale state). -func newTestServerWithCache(t *testing.T, st store.Interface, workers *workercache.Cache) *Server { - t.Helper() - - certificateAuthority, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour) - if err != nil { - t.Fatalf("generate CA: %v", err) - } - certificateAuthorityPool := &localca.ConcretePool{ - CAs: []*localca.CA{certificateAuthority}, - ActiveForSigning: "test-actor-ca", - } - - jwtAuthority, err := localjwtauthority.GenerateECDSAP256Authority("1") - if err != nil { - t.Fatalf("while generating JWT authority: %v", err) - } - jwtAuthorityPool := &localjwtauthority.ConcretePool{ - Authorities: []*localjwtauthority.Authority{jwtAuthority}, - ActiveForSigning: "1", - } - - return New("issuer", jwtAuthorityPool, certificateAuthorityPool, st, workers) -} - -func TestMintJWTRequiresConfiguredJWTProvider(t *testing.T) { - srv := &Server{actorIdentityJWTIssuer: "https://kubernetes.example"} - for _, tt := range []struct { - name string - ctx context.Context - code codes.Code - }{ - {name: "no principal", ctx: context.Background(), code: codes.Unauthenticated}, - { - name: "mTLS principal", - ctx: principal.InjectContext(context.Background(), principal.PrincipalInfo{ID: "spiffe://caller", Kind: principal.KindMTLS}), - code: codes.Unauthenticated, - }, - { - name: "different JWT provider", - ctx: principal.InjectContext(context.Background(), principal.PrincipalInfo{ID: "user", Kind: principal.KindJWT, Issuer: "https://accounts.google.com"}), - code: codes.PermissionDenied, - }, - } { - t.Run(tt.name, func(t *testing.T) { - _, err := srv.MintJWT(tt.ctx, &ateapipb.MintJWTRequest{}) - if got := status.Code(err); got != tt.code { - t.Fatalf("MintJWT() code = %v, want %v (err = %v)", got, tt.code, err) - } - }) - } -} - -// newCSR returns a DER-encoded, correctly self-signed CSR. -func newCSR(t *testing.T) []byte { - t.Helper() - _, priv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatalf("generate key: %v", err) - } - der, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ - Subject: pkix.Name{CommonName: "actor"}, - }, priv) - if err != nil { - t.Fatalf("create CSR: %v", err) - } - return der -} - -func mintCertRequest(t *testing.T, actorUID string) *ateapipb.MintCertRequest { - t.Helper() - return &ateapipb.MintCertRequest{ - Worker: &ateapipb.ObjectRef{Name: testWorkerName}, - ExpectedActorUid: actorUID, - CertificateSigningRequest: newCSR(t), - Purpose: ateapipb.ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_ATUNNEL, - } -} - -// actorFixture describes the actor/worker pair seeded into the store. -type actorFixture struct { - state ateapipb.ActorState - workerNode string - // actorWorkerName overrides the Worker the actor points at while leaving - // the requesting worker unchanged, simulating a stale reciprocal - // assignment. - actorWorkerName string - // assignedTo overrides the actor the worker claims to be hosting. The zero - // value means the worker is assigned to the seeded actor. - assignedTo resources.ActorRef - // unassigned seeds the worker with no assignment at all, as pause, suspend - // and crash leave it once they have released it. - unassigned bool - // noPlacement seeds the actor with no worker assignment. - noPlacement bool - // noWorker skips seeding the worker record entirely. - noWorker bool - // mismatchedUID simulates a worker assigned to an actor with the same name/atespace but a different UID. - mismatchedUID bool -} - -// seedActor writes an actor, and normally its hosting worker, into st. -func seedActor(t *testing.T, ctx context.Context, st store.Interface, f actorFixture) { - t.Helper() - - actorRef := resources.ActorRef{Atespace: testAtespace, Name: testActorName} - - actor := &ateapipb.Actor{ - Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: actorRef.Name}, - Status: &ateapipb.ActorStatus{State: f.state}, - ActorTemplate: &ateapipb.ObjectRef{Atespace: "ate-demo", Name: "counter"}, - } - if !f.noPlacement { - workerName := testWorkerName - if f.actorWorkerName != "" { - workerName = f.actorWorkerName - } - actor.Status.WorkerAssignment = &ateapipb.WorkerAssignment{ - Worker: &ateapipb.ObjectRef{Name: workerName}, - WorkerNamespace: testPodNS, - WorkerPool: testPool, - WorkerPod: testWorkerPod, - WorkerPodUid: testWorkerPodUID, - } - } - created := storetest.MustCreateActor(t, ctx, st, actor) - - if f.noWorker { - return - } - assigned := f.assignedTo - if assigned == (resources.ActorRef{}) { - assigned = actorRef - } - assignedActorUID := created.GetMetadata().GetUid() - if f.mismatchedUID || assigned != actorRef { - assignedActorUID = "other-actor-uid" - } - worker := &ateapipb.Worker{ - Metadata: &ateapipb.ResourceMetadata{Name: testWorkerName}, - WorkerNamespace: testPodNS, - WorkerPool: testPool, - WorkerPod: testWorkerPod, - WorkerPodUid: testWorkerPodUID, - NodeName: f.workerNode, - Status: &ateapipb.WorkerStatus{State: ateapipb.WorkerState_WORKER_STATE_ACTIVE}, - } - if _, err := st.CreateWorker(ctx, worker); err != nil { - t.Fatalf("seed worker: %v", err) - } - if f.unassigned { - return - } - bindActor(t, ctx, st, testWorkerName, &ateapipb.ActorAssignment{ - Actor: assigned.ToObjectRef(), - ActorUid: assignedActorUID, - }) -} - -// bindActor places an actor on a worker at whatever version it is currently at. -func bindActor(t *testing.T, ctx context.Context, st store.Interface, workerName string, assignment *ateapipb.ActorAssignment) { - t.Helper() - if err := st.BindActorToWorker(ctx, workerName, assignment, nil); err != nil { - t.Fatalf("bind actor %s to worker: %v", assignment.GetActorUid(), err) - } -} - -// runningOnNode is the fixture for a healthy actor hosted on nodeName. -func runningOnNode(nodeName string) actorFixture { - return actorFixture{state: ateapipb.ActorState_ACTOR_STATE_RUNNING, workerNode: nodeName} -} - -// TestMintCertAuthorization covers the gate deciding whether a caller may mint -// a certificate for the requested actor. -func TestMintCertAuthorization(t *testing.T) { - // ptr is needed because "" is itself a case under test, so the zero value - // cannot double as "use the default". - ptr := func(s string) *string { return &s } - - for name, tc := range map[string]struct { - // cert builds the caller's certificate. Nil means a well-formed atelet - // on the node hosting the actor. - cert func(t *testing.T) *x509.Certificate - // noPeer calls the RPC with no transport credentials at all. - noPeer bool - - fixture actorFixture - - // Request fields override their defaults when non-nil. A nil worker - // override leaves the request pointing at the seeded worker. - worker *ateapipb.ObjectRef - noWorker bool - expectedActorUID *string - - wantCode codes.Code - }{ - "atelet on the hosting node mints for a running actor": { - fixture: runningOnNode(testNode), - wantCode: codes.OK, - }, - "actor is in ACTOR_STATE_DELETING with active worker assignment": { - fixture: actorFixture{state: ateapipb.ActorState_ACTOR_STATE_DELETING, workerNode: testNode}, - wantCode: codes.FailedPrecondition, - }, - "caller presented no certificate": { - noPeer: true, - fixture: runningOnNode(testNode), - wantCode: codes.Unauthenticated, - }, - "caller is not the atelet service account": { - cert: func(t *testing.T) *x509.Certificate { - id := ateletauthtest.PodIdentityOn(testNode) - id.ServiceAccountName = "some-workload" - return ateletauthtest.Cert(t, path.Join("ns", ateletauth.Namespace, "sa", "some-workload"), id) - }, - fixture: runningOnNode(testNode), - wantCode: codes.PermissionDenied, - }, - "caller is an atelet in the wrong namespace": { - cert: func(t *testing.T) *x509.Certificate { - id := ateletauthtest.PodIdentityOn(testNode) - id.Namespace = "someone-elses-system" - return ateletauthtest.Cert(t, path.Join("ns", "someone-elses-system", "sa", ateletauth.ServiceAccount), id) - }, - fixture: runningOnNode(testNode), - wantCode: codes.PermissionDenied, - }, - "certificate carries no SPIFFE URI": { - cert: func(t *testing.T) *x509.Certificate { - return ateletauthtest.Cert(t, "", ateletauthtest.PodIdentityOn(testNode)) - }, - fixture: runningOnNode(testNode), - wantCode: codes.PermissionDenied, - }, - "certificate carries no PodIdentity extension": { - cert: func(t *testing.T) *x509.Certificate { - return ateletauthtest.Cert(t, path.Join("ns", ateletauth.Namespace, "sa", ateletauth.ServiceAccount), nil) - }, - fixture: runningOnNode(testNode), - wantCode: codes.PermissionDenied, - }, - "actor does not exist": { - fixture: actorFixture{ - state: ateapipb.ActorState_ACTOR_STATE_RUNNING, - workerNode: testNode, - assignedTo: resources.ActorRef{Atespace: testAtespace, Name: "no-such-actor"}, - }, - wantCode: codes.PermissionDenied, - }, - "actor exists under a different atespace": { - fixture: actorFixture{ - state: ateapipb.ActorState_ACTOR_STATE_RUNNING, - workerNode: testNode, - assignedTo: resources.ActorRef{Atespace: "some-other-atespace", Name: testActorName}, - }, - wantCode: codes.PermissionDenied, - }, - "actor is hosted on a different node": { - fixture: runningOnNode(testOtherNode), - wantCode: codes.PermissionDenied, - }, - "worker names a different Pod UID": { - fixture: runningOnNode(testNode), - worker: &ateapipb.ObjectRef{Name: "9a2f7b81-4c60-4d13-8e5a-3f0b6c8d1e27"}, - wantCode: codes.PermissionDenied, - }, - "worker is assigned to a different actor": { - fixture: actorFixture{ - state: ateapipb.ActorState_ACTOR_STATE_RUNNING, - workerNode: testNode, - assignedTo: resources.ActorRef{Atespace: testAtespace, Name: "someone-else"}, - }, - wantCode: codes.PermissionDenied, - }, - "worker is assigned to an actor with same name and atespace but different UID": { - fixture: actorFixture{ - state: ateapipb.ActorState_ACTOR_STATE_RUNNING, - workerNode: testNode, - mismatchedUID: true, - }, - wantCode: codes.PermissionDenied, - }, - "actor points to a different worker": { - fixture: actorFixture{ - state: ateapipb.ActorState_ACTOR_STATE_RUNNING, - workerNode: testNode, - actorWorkerName: "7c3d9e15-2a48-4b6f-9d01-8e5a3f0b6c8d", - }, - wantCode: codes.PermissionDenied, - }, - "hosting worker record is missing": { - fixture: actorFixture{ - state: ateapipb.ActorState_ACTOR_STATE_RUNNING, - workerNode: testNode, - noWorker: true, - }, - wantCode: codes.PermissionDenied, - }, - "actor has no placement": { - fixture: actorFixture{ - state: ateapipb.ActorState_ACTOR_STATE_RUNNING, - workerNode: testNode, - noPlacement: true, - }, - wantCode: codes.FailedPrecondition, - }, - "worker has been released": { - fixture: actorFixture{ - state: ateapipb.ActorState_ACTOR_STATE_RUNNING, - workerNode: testNode, - unassigned: true, - }, - wantCode: codes.PermissionDenied, - }, - "worker is unset": { - fixture: runningOnNode(testNode), - noWorker: true, - wantCode: codes.InvalidArgument, - }, - "worker name is empty": { - fixture: runningOnNode(testNode), - worker: &ateapipb.ObjectRef{}, - wantCode: codes.InvalidArgument, - }, - "worker carries an atespace": { - fixture: runningOnNode(testNode), - worker: &ateapipb.ObjectRef{Atespace: testAtespace, Name: testWorkerName}, - wantCode: codes.InvalidArgument, - }, - "expected actor UID is empty": { - fixture: runningOnNode(testNode), - expectedActorUID: ptr(""), - wantCode: codes.InvalidArgument, - }, - } { - t.Run(name, func(t *testing.T) { - ctx := context.Background() - st, cleanup := storetest.SetupTestStore(t) - defer cleanup() - - seedActor(t, ctx, st, tc.fixture) - srv := newTestServer(t, st) - - var callerCert *x509.Certificate - switch { - case tc.noPeer: - case tc.cert != nil: - callerCert = tc.cert(t) - default: - callerCert = ateletauthtest.CertOn(t, testNode) - } - - actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName}) - if err != nil { - t.Fatalf("read seeded actor: %v", err) - } - req := mintCertRequest(t, actor.GetMetadata().GetUid()) - switch { - case tc.noWorker: - req.Worker = nil - case tc.worker != nil: - req.Worker = tc.worker - } - if tc.expectedActorUID != nil { - req.ExpectedActorUid = *tc.expectedActorUID - } - resp, err := srv.MintCert(ateletauthtest.ContextWith(callerCert), req) - if got := status.Code(err); got != tc.wantCode { - t.Fatalf("MintCert() code = %v (err = %v), want %v", got, err, tc.wantCode) - } - if tc.wantCode == codes.PermissionDenied { - // Denials are deliberately indistinguishable (see denyMint): the - // message must not vary with why the mint was refused, or a - // caller could probe workers it is not entitled to. - msg := status.Convert(err).Message() - if msg != "caller is not permitted" && - msg != "caller is not permitted to mint credentials for this actor" { - t.Errorf("MintCert() denial leaks its reason: %q", msg) - } - } - if tc.wantCode != codes.OK { - if resp != nil { - t.Errorf("MintCert() returned a response alongside an error") - } - return - } - - if len(resp.GetActorCertificates()) == 0 { - t.Fatal("MintCert() returned no certificates") - } - leaf, err := x509.ParseCertificate(resp.GetActorCertificates()[0]) - if err != nil { - t.Fatalf("parse minted certificate: %v", err) - } - want := "spiffe://substrate-actor.local/atespace/" + testAtespace + "/actor/" + testActorName - if len(leaf.URIs) != 1 || leaf.URIs[0].String() != want { - t.Errorf("minted SPIFFE URI = %v, want %q", leaf.URIs, want) - } - }) - } -} - -func TestMintCertRejectsUnsupportedPurpose(t *testing.T) { - server := newTestServer(t, nil) - for name, purpose := range map[string]ateapipb.ActorCertificatePurpose{ - "unspecified": ateapipb.ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED, - "unknown": ateapipb.ActorCertificatePurpose(99), - } { - t.Run(name, func(t *testing.T) { - _, err := server.MintCert(ateletauthtest.ContextWith(ateletauthtest.CertOn(t, testNode)), &ateapipb.MintCertRequest{Purpose: purpose}) - if got := status.Code(err); got != codes.InvalidArgument { - t.Fatalf("MintCert() code = %v (err = %v), want %v", got, err, codes.InvalidArgument) - } - }) - } -} - -// mintCertFor seeds a running actor and mints a certificate for it, returning -// the parsed leaf alongside the UID the store assigned the actor. The request -// is built from that UID, since it is only known once the actor exists. -func mintCertFor(t *testing.T, request func(actorUID string) *ateapipb.MintCertRequest) (*x509.Certificate, string, error) { - t.Helper() - - ctx := context.Background() - st, cleanup := storetest.SetupTestStore(t) - t.Cleanup(cleanup) - - seedActor(t, ctx, st, runningOnNode(testNode)) - actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName}) - if err != nil { - t.Fatalf("read seeded actor: %v", err) - } - actorUID := actor.GetMetadata().GetUid() - if actorUID == "" { - t.Fatal("seeded actor has no UID; the store is expected to assign one") - } - - resp, err := newTestServer(t, st).MintCert(ateletauthtest.ContextWith(ateletauthtest.CertOn(t, testNode)), request(actorUID)) - if err != nil { - return nil, actorUID, err - } - if len(resp.GetActorCertificates()) == 0 { - t.Fatal("MintCert() returned no certificates") - } - leaf, err := x509.ParseCertificate(resp.GetActorCertificates()[0]) - if err != nil { - t.Fatalf("parse minted certificate: %v", err) - } - return leaf, actorUID, nil -} - -// TestMintCertEmbedsActorIdentity checks that a minted certificate carries the -// ActorIdentity extension, naming the actor the store knows about. -func TestMintCertEmbedsActorIdentity(t *testing.T) { - leaf, actorUID, err := mintCertFor(t, func(actorUID string) *ateapipb.MintCertRequest { - return mintCertRequest(t, actorUID) - }) - if err != nil { - t.Fatalf("MintCert(): %v", err) - } - - got, err := substratex509.ActorIdentityFromCertificate(leaf) - if err != nil { - t.Fatalf("ActorIdentityFromCertificate: %v", err) - } - if got == nil { - t.Fatal("minted certificate carries no ActorIdentity extension") - } - want := &substratex509.ActorIdentity{ - Atespace: testAtespace, - ActorName: testActorName, - ActorUid: actorUID, - Purpose: substratex509.ActorIdentityPurposeAtunnel, - } - if *got != *want { - t.Errorf("ActorIdentity = %+v, want %+v", got, want) - } -} - -// TestMintCertActorUID checks that expected_actor_uid rejects a request that -// crossed an actor reassignment. It never decides the certificate identity, -// which always comes from ateapi state. -func TestMintCertActorUID(t *testing.T) { - for name, tc := range map[string]struct { - requestUID func(actorUID string) string - wantCode codes.Code - }{ - "Matching": {requestUID: func(actorUID string) string { return actorUID }, wantCode: codes.OK}, - "Stale": {requestUID: func(string) string { return "9d1f7b06-3c58-4a2e-8b40-5f7c1e9a2d63" }, wantCode: codes.FailedPrecondition}, - } { - t.Run(name, func(t *testing.T) { - leaf, actorUID, err := mintCertFor(t, func(actorUID string) *ateapipb.MintCertRequest { - req := mintCertRequest(t, actorUID) - req.ExpectedActorUid = tc.requestUID(actorUID) - return req - }) - if got := status.Code(err); got != tc.wantCode { - t.Fatalf("MintCert() code = %v (err = %v), want %v", got, err, tc.wantCode) - } - if tc.wantCode != codes.OK { - return - } - - identity, err := substratex509.ActorIdentityFromCertificate(leaf) - if err != nil { - t.Fatalf("ActorIdentityFromCertificate: %v", err) - } - if identity == nil { - t.Fatal("minted certificate carries no ActorIdentity extension") - } - if identity.ActorUid != actorUID { - t.Errorf("ActorIdentity.ActorUid = %q, want the stored UID %q", identity.ActorUid, actorUID) - } - }) - } -} - -// TestMintCertActorState pins down that the actor's state does not gate -// minting: an actor still assigned to a worker on the caller's node gets a -// credential whatever state it carries, except while it is being deleted. -// -// ACTOR_STATE_RESUMING is the case that matters in practice. atelet mints -// while serving the Run/Restore RPC that ateapi issues before marking the -// actor RUNNING, so gating on RUNNING would make every resume unsatisfiable. -// -// The terminal states below are seeded with a worker assignment that the -// control plane would already have cleared, so they are not reachable in a -// healthy system; they are exercised to record that the assignment, not the -// state, is what the decision rests on. Enumerating the enum rather than -// listing states means a state added later is covered without editing this -// test. -func TestMintCertActorState(t *testing.T) { - for value, name := range ateapipb.ActorState_name { - actorState := ateapipb.ActorState(value) - wantCode := codes.OK - if actorState == ateapipb.ActorState_ACTOR_STATE_DELETING { - wantCode = codes.FailedPrecondition - } - t.Run(name, func(t *testing.T) { - ctx := context.Background() - st, cleanup := storetest.SetupTestStore(t) - defer cleanup() - - seedActor(t, ctx, st, actorFixture{state: actorState, workerNode: testNode}) - srv := newTestServer(t, st) - - actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName}) - if err != nil { - t.Fatal(err) - } - _, err = srv.MintCert(ateletauthtest.ContextWith(ateletauthtest.CertOn(t, testNode)), mintCertRequest(t, actor.GetMetadata().GetUid())) - if got := status.Code(err); got != wantCode { - t.Errorf("MintCert() code = %v (err = %v), want %v", got, err, wantCode) - } - }) - } -} - -// TestMintCertDeniesUnassignedActorWhateverItsState checks that the placement -// checks — not the state — are what stops a departed actor. A RUNNING actor -// whose worker has been released is refused just as a SUSPENDED one is. -func TestMintCertDeniesUnassignedActorWhateverItsState(t *testing.T) { - for name, actorState := range map[string]ateapipb.ActorState{ - "Running": ateapipb.ActorState_ACTOR_STATE_RUNNING, - "Suspended": ateapipb.ActorState_ACTOR_STATE_SUSPENDED, - } { - t.Run(name, func(t *testing.T) { - ctx := context.Background() - st, cleanup := storetest.SetupTestStore(t) - defer cleanup() - - // The worker still exists on the caller's node but has been released, - // which is what pause, suspend and crash all do before writing the - // terminal state. - seedActor(t, ctx, st, actorFixture{ - state: actorState, - workerNode: testNode, - unassigned: true, - }) - srv := newTestServer(t, st) - - actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName}) - if err != nil { - t.Fatal(err) - } - _, err = srv.MintCert(ateletauthtest.ContextWith(ateletauthtest.CertOn(t, testNode)), mintCertRequest(t, actor.GetMetadata().GetUid())) - if got := status.Code(err); got != codes.PermissionDenied { - t.Errorf("MintCert() code = %v (err = %v), want %v", got, err, codes.PermissionDenied) - } - }) - } -} - -// TestMintCertAuthorizesBeforeSigning checks that the gate runs before any CSR -// parsing or CA material is touched. An unauthorized caller must be rejected -// with PermissionDenied even when the rest of the request is unusable, so that -// a failure downstream of the gate can never mask the authorization decision. -func TestMintCertAuthorizesBeforeSigning(t *testing.T) { - ctx := context.Background() - st, cleanup := storetest.SetupTestStore(t) - defer cleanup() - - seedActor(t, ctx, st, runningOnNode(testOtherNode)) - - // A server whose CA pool file does not exist: reaching the signing path at - // all would surface as Internal rather than PermissionDenied. - workers := workercache.New(st, time.Hour) - cacheCtx, cancel := context.WithCancel(ctx) - defer cancel() - if err := workers.Start(cacheCtx); err != nil { - t.Fatal(err) - } - - certificateAuthority, err := localca.GenerateCA("test-actor-ca", localca.KeyTypeED25519, 24*time.Hour) - if err != nil { - t.Fatalf("generate CA: %v", err) - } - certificateAuthorityPool := &localca.ConcretePool{ - CAs: []*localca.CA{certificateAuthority}, - ActiveForSigning: "test-actor-ca", - } - - jwtAuthority, err := localjwtauthority.GenerateECDSAP256Authority("1") - if err != nil { - t.Fatalf("while generating JWT authority: %v", err) - } - jwtAuthorityPool := &localjwtauthority.ConcretePool{ - Authorities: []*localjwtauthority.Authority{jwtAuthority}, - ActiveForSigning: "1", - } - - srv := New("issuer", jwtAuthorityPool, certificateAuthorityPool, st, workers) - - actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName}) - if err != nil { - t.Fatal(err) - } - req := mintCertRequest(t, actor.GetMetadata().GetUid()) - req.CertificateSigningRequest = []byte("not a CSR") - _, err = srv.MintCert(ateletauthtest.ContextWith(ateletauthtest.CertOn(t, testNode)), req) - if got := status.Code(err); got != codes.PermissionDenied { - t.Errorf("MintCert() code = %v (err = %v), want %v", got, err, codes.PermissionDenied) - } -} - -func TestValidateMintJWTRequest(t *testing.T) { - // This test verifies validation of user input for minting a JWT. - validReq := func(mods ...func(req *ateapipb.MintJWTRequest)) *ateapipb.MintJWTRequest { - req := &ateapipb.MintJWTRequest{ - Audience: []string{"aud1"}, - Atespace: "as1", - ActorName: "actor1", - ActorUid: "01234567-89ab-cdef-0123-456789abcdef", - } - for _, m := range mods { - m(req) - } - return req - } - - tests := []struct { - name string - req *ateapipb.MintJWTRequest - want field.ErrorList - }{{ - "valid", - validReq(), - nil, - }, { - "missing audience", - validReq(func(r *ateapipb.MintJWTRequest) { r.Audience = nil }), - field.ErrorList{field.Required(field.NewPath("audience"), "")}, - }, { - "too many audiences", - validReq(func(r *ateapipb.MintJWTRequest) { - r.Audience = make([]string, 17) - for i := range r.Audience { - r.Audience[i] = fmt.Sprintf("https://svc-%d.example.com", i) - } - }), - field.ErrorList{field.TooMany(field.NewPath("audience"), 17, 16).WithOrigin("maxItems")}, - }, { - "duplicate audience entry", - validReq(func(r *ateapipb.MintJWTRequest) { - r.Audience = []string{"https://a.example.com", "https://a.example.com"} - }), - field.ErrorList{field.Duplicate(field.NewPath("audience").Index(1), nil)}, - }, { - "audience entry too long", - validReq(func(r *ateapipb.MintJWTRequest) { r.Audience = []string{strings.Repeat("a", 513)} }), - field.ErrorList{field.TooLong(field.NewPath("audience").Index(0), nil, 512).WithOrigin("maxLength")}, - }, { - "missing atespace", - validReq(func(r *ateapipb.MintJWTRequest) { r.Atespace = "" }), - field.ErrorList{field.Required(field.NewPath("atespace"), "")}, - }, { - "invalid atespace", - validReq(func(r *ateapipb.MintJWTRequest) { r.Atespace = "AS1" }), - field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, - }, { - "missing actor_name", - validReq(func(r *ateapipb.MintJWTRequest) { r.ActorName = "" }), - field.ErrorList{field.Required(field.NewPath("actor_name"), "")}, - }, { - "invalid actor_name", - validReq(func(r *ateapipb.MintJWTRequest) { r.ActorName = "invalid value" }), - field.ErrorList{field.Invalid(field.NewPath("actor_name"), nil, "").WithOrigin("format=k8s-short-name")}, - }, { - "unspecified actor_uid", - validReq(func(r *ateapipb.MintJWTRequest) { r.ActorUid = "" }), - nil, - }, { - "invalid actor_uid", - validReq(func(r *ateapipb.MintJWTRequest) { r.ActorUid = "not a uid" }), - field.ErrorList{field.Invalid(field.NewPath("actor_uid"), nil, "").WithOrigin("format=k8s-uuid")}, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assertValidateErr(t, validateMintJWTRequest(context.Background(), tt.req), tt.want) - }) - } -} - -func TestValidateMintCertRequest(t *testing.T) { - // This test verifies validation of user input for minting a certificate. - validReq := func(mods ...func(req *ateapipb.MintCertRequest)) *ateapipb.MintCertRequest { - req := &ateapipb.MintCertRequest{ - Worker: &ateapipb.ObjectRef{Name: "worker1"}, - CertificateSigningRequest: []byte{0x01}, - ExpectedActorUid: "01234567-89ab-cdef-0123-456789abcdef", - Purpose: ateapipb.ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_ATUNNEL, - } - for _, m := range mods { - m(req) - } - return req - } - - tests := []struct { - name string - req *ateapipb.MintCertRequest - want field.ErrorList - }{{ - "valid", - validReq(), - nil, - }, { - "oversized certificate_signing_request", - validReq(func(r *ateapipb.MintCertRequest) { r.CertificateSigningRequest = make([]byte, 16385) }), - field.ErrorList{field.TooLong(field.NewPath("certificate_signing_request"), nil, 16384)}, - }, { - "missing worker", - validReq(func(r *ateapipb.MintCertRequest) { r.Worker = nil }), - field.ErrorList{field.Required(field.NewPath("worker"), "")}, - }, { - "worker.atespace must be empty", - validReq(func(r *ateapipb.MintCertRequest) { r.Worker.Atespace = "as1" }), - field.ErrorList{field.Forbidden(field.NewPath("worker", "atespace"), "")}, - }, { - "missing worker.name", - validReq(func(r *ateapipb.MintCertRequest) { r.Worker.Name = "" }), - field.ErrorList{field.Required(field.NewPath("worker", "name"), "")}, - }, { - "invalid worker.name", - validReq(func(r *ateapipb.MintCertRequest) { r.Worker.Name = "invalid value" }), - field.ErrorList{field.Invalid(field.NewPath("worker", "name"), nil, "").WithOrigin("format=k8s-short-name")}, - }, { - "missing certificate_signing_request", - validReq(func(r *ateapipb.MintCertRequest) { r.CertificateSigningRequest = nil }), - field.ErrorList{field.Required(field.NewPath("certificate_signing_request"), "")}, - }, { - "missing expected_actor_uid", - validReq(func(r *ateapipb.MintCertRequest) { r.ExpectedActorUid = "" }), - field.ErrorList{field.Required(field.NewPath("expected_actor_uid"), "")}, - }, { - "invalid expected_actor_uid", - validReq(func(r *ateapipb.MintCertRequest) { r.ExpectedActorUid = "not a uid" }), - field.ErrorList{field.Invalid(field.NewPath("expected_actor_uid"), nil, "").WithOrigin("format=k8s-uuid")}, - }, { - "unspecified purpose", - validReq(func(r *ateapipb.MintCertRequest) { - r.Purpose = ateapipb.ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED - }), - field.ErrorList{field.Required(field.NewPath("purpose"), "")}, - }, { - "out-of-range purpose", - validReq(func(r *ateapipb.MintCertRequest) { r.Purpose = ateapipb.ActorCertificatePurpose(99) }), - field.ErrorList{field.Invalid(field.NewPath("purpose"), nil, "").WithOrigin("maximum")}, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assertValidateErr(t, validateMintCertRequest(context.Background(), tt.req), tt.want) - }) - } -} diff --git a/cmd/ateapi/internal/actoridentity/main_test.go b/cmd/ateapi/internal/actoridentity/main_test.go deleted file mode 100644 index b4ce4cec00..0000000000 --- a/cmd/ateapi/internal/actoridentity/main_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2026 Google LLC -// -// 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 actoridentity - -import ( - "testing" - - "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest" -) - -func TestMain(m *testing.M) { - storetest.RunTests(m) -} diff --git a/cmd/ateapi/internal/controlapi/actor.go b/cmd/ateapi/internal/controlapi/actor.go index cf66109d4b..7ca1651e56 100644 --- a/cmd/ateapi/internal/controlapi/actor.go +++ b/cmd/ateapi/internal/controlapi/actor.go @@ -16,17 +16,27 @@ package controlapi import ( "context" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" "errors" "fmt" + "log/slog" + "net/url" + "path" "slices" "time" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" + "github.com/agent-substrate/substrate/internal/actoridjwt" "github.com/agent-substrate/substrate/internal/ateattr" "github.com/agent-substrate/substrate/internal/resources" + "github.com/agent-substrate/substrate/internal/substratex509" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "go.opentelemetry.io/otel/attribute" "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "k8s.io/apimachinery/pkg/api/operation" @@ -537,3 +547,158 @@ func ValidateCustom_UpdateActorRequest_Actor(ctx context.Context, op operation.O errs = append(errs, validate.RequiredValue(ctx, op, fldPath.Child("metadata", "atespace"), &actor.Metadata.Atespace, nil)...) return errs } + +func (s *RPCService) MintActorJWT(ctx context.Context, req *ateapipb.MintActorJWTRequest) (*ateapipb.MintActorJWTResponse, error) { + // TODO(authz): Authorization layer needs to check whether the caller has + // the mintActorJWT permission/relation with this actor. This could be an + // atelet (via the relationship of the atelet running the actor), or the + // egress gateway (via a cluster-level grant?) + + // Verify that this actor exists in the store. It doesn't need to be + // running, since we may need to issue JWTs during actor boot / resume. + dbActor, err := s.impl.GetActor(ctx, resources.ActorRefFromObjectRef(req.GetActor())) + if errors.Is(err, store.ErrNotFound) { + return nil, status.Error(codes.NotFound, "actor not found") + } else if err != nil { + return nil, fmt.Errorf("while retrieving actor: %w", err) + } + if dbActor.GetMetadata().GetUid() != req.GetActorUid() { + return nil, status.Error(codes.Aborted, "conflict; actor has been deleted and recreated") + } + + // We only issue tokens with audience bindings. + if len(req.GetAudience()) == 0 { + return nil, fmt.Errorf("at least one audience must be requested") + } + + actorClaims := &actoridjwt.Claims{ + // TODO(identity): This needs to be configurable per-install. The user + // needs to make sure that the OIDC discovery docs are accessible at + // this URL, so that relying parties can verify the JWTs. + Issuer: "https://api.ate-system.svc", + // TODO(identity): this format is very likely going to change. + Subject: fmt.Sprintf("atespaces:%s:actors:%s", dbActor.GetMetadata().GetAtespace(), dbActor.GetMetadata().GetName()), + Audiences: req.GetAudience(), + Expiration: time.Now().Add(15 * time.Minute), + NotBefore: time.Now().Add(-5 * time.Minute), + IssuedAt: time.Now(), + JTI: rand.Text(), + + Substrate: actoridjwt.SubstrateClaims{ + Atespace: dbActor.GetMetadata().GetAtespace(), + ActorName: dbActor.GetMetadata().GetName(), + ActorUID: dbActor.GetMetadata().GetUid(), + }, + } + + actorJWT, err := s.actorIDJWTPool.SignJWT(actorClaims) + if err != nil { + return nil, fmt.Errorf("while signing actor JWT: %w", err) + } + + return &ateapipb.MintActorJWTResponse{ + ActorJwt: actorJWT, + }, nil +} + +func (s *RPCService) MintActorCertificate(ctx context.Context, req *ateapipb.MintActorCertificateRequest) (*ateapipb.MintActorCertificateResponse, error) { + // TODO(authz): Authorization layer needs to check whether the caller has + // the mintActorCertificate permission/relation with this actor. This + // could be an atelet (via the relationship of the atelet running the + // actor), or the egress gateway (via a cluster-level grant?) + + // Check that the caller authenticated with a client certificate --- we + // should not allow bootstrapping a proof-of-possession credential from a + // bearer credential. Note, we don't care that it was a certificate issued + // by Substrate, or something else. + // + // TODO(authz): Perhaps this can be handled with an OpenFGA condition. + p, ok := peer.FromContext(ctx) + if !ok { + return nil, status.Errorf(codes.Unauthenticated, "no peer transport information found") + } + tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) + if !ok { + return nil, status.Errorf(codes.Unauthenticated, "unexpected peer transport credentials") + } + if len(tlsInfo.State.PeerCertificates) == 0 { + return nil, status.Errorf(codes.Unauthenticated, "could not verify peer certificate") + } + + // Verify that this actor exists in the store. It doesn't need to be + // running, since we may need to issue certificates during actor boot / resume. + dbActor, err := s.impl.GetActor(ctx, resources.ActorRefFromObjectRef(req.GetActor())) + if errors.Is(err, store.ErrNotFound) { + return nil, status.Error(codes.NotFound, "actor not found") + } else if err != nil { + return nil, fmt.Errorf("while retrieving actor: %w", err) + } + if dbActor.GetMetadata().GetUid() != req.GetActorUid() { + return nil, status.Error(codes.Aborted, "conflict; actor has been deleted and recreated") + } + + // Parse the CSR + csr, err := x509.ParseCertificateRequest(req.GetCertificateSigningRequest()) + if err != nil { + return nil, fmt.Errorf("while parsing CSR: %w", err) + } + if err := csr.CheckSignature(); err != nil { + slog.ErrorContext(ctx, "Failed to verify CSR signature", slog.Any("err", err)) + return nil, status.Errorf(codes.InvalidArgument, "Failed to verify CSR signature") + } + + // TODO(identity): Atunnel certificates should probably have a separate RPC, + // since different callers will be authorized to get atunnel certificates vs + // actor self-identity certificates. + var template *x509.Certificate + switch req.GetPurpose() { + case ateapipb.ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_ATUNNEL: + template = &x509.Certificate{ + URIs: []*url.URL{ + { + Scheme: "spiffe", + // TODO(identity): Must be configurable per-install, so that each install can set it to a unique value. + Host: "substrate-actor.local", + // TODO(identity): Prefix with "atunnel" to prevent + // confusion between atunnel and an actor pretending to be + // an atunnel. + Path: path.Join("atespace", dbActor.GetMetadata().GetAtespace(), "actor", dbActor.GetMetadata().GetName()), + }, + }, + NotBefore: time.Now().Add(-5 * time.Minute), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: false, + Issuer: pkix.Name{ + CommonName: "api.ate-system.svc.cluster.local", + }, + } + default: + return nil, status.Errorf(codes.InvalidArgument, "certificate purpose must be specified") + } + + err = substratex509.AddActorIdentityToCertificate( + &substratex509.ActorIdentity{ + Atespace: dbActor.GetMetadata().GetAtespace(), + ActorName: dbActor.GetMetadata().GetName(), + ActorUid: dbActor.GetMetadata().GetUid(), + Purpose: substratex509.ActorIdentityPurposeAtunnel, + }, + template, + ) + if err != nil { + return nil, fmt.Errorf("while adding Substrate extension: %w", err) + } + + // Sign and return the actor cert. + chain, err := s.actorIDCAPool.CreateCertificate(template, csr.PublicKey) + if err != nil { + return nil, fmt.Errorf("while signing certificate: %w", err) + } + + return &ateapipb.MintActorCertificateResponse{ + ActorCertificates: chain, + }, nil +} diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go index 3a323a8c4b..7cc4d16c24 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go @@ -3415,10 +3415,12 @@ func TestCreateActor_RejectsUnknownRequestFields(t *testing.T) { // capacity would stay booked until the Worker itself went away. func TestDeleteActor_ReleasesAnAssignmentTheActorDoesNotReference(t *testing.T) { ns := namespaceForTest("ns-delete-orphan") + tc := setupTest(t, ns) defer tc.cleanup() createTemplate(t, tc, ns) + createWorkerPool(t, tc, ns, "pool-1", nil) podUID := createWorkerPod(t, tc, ns, "worker-1", "node-1", "pool-1") @@ -3430,6 +3432,7 @@ func TestDeleteActor_ReleasesAnAssignmentTheActorDoesNotReference(t *testing.T) if err != nil { t.Fatalf("CreateActor failed: %v", err) } + actorUID := actor.GetMetadata().GetUid() // Bind straight through the store, leaving the Actor's backlink unset: @@ -3459,3 +3462,35 @@ func TestDeleteActor_ReleasesAnAssignmentTheActorDoesNotReference(t *testing.T) t.Errorf("worker still books %d actors after the Actor was deleted, want 0", got) } } + +func TestMintActorJWT_Success(t *testing.T) { + ns := namespaceForTest("ns-mintactorjwt-success") + + tc := setupTest(t, ns) + defer tc.cleanup() + + createTemplate(t, tc, ns) + + createResp, err := tc.client.CreateActor(t.Context(), &ateapipb.CreateActorRequest{ + Actor: &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{ + Atespace: testAtespace, + Name: "id1", + }, + ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"}, + WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{"tier": "free"}}, + Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_RUNNING}, + }, + }) + if err != nil { + t.Fatalf("CreateActor failed: %v", err) + } + _, err = tc.client.MintActorJWT(t.Context(), &ateapipb.MintActorJWTRequest{ + Actor: &ateapipb.ObjectRef{ + Atespace: createResp.GetMetadata().GetAtespace(), + Name: createResp.GetMetadata().GetName(), + }, + ActorUid: createResp.GetMetadata().GetUid(), + Audience: []string{"foo"}, + }) +} diff --git a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go index 05d7a3a615..339d2521a6 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go @@ -27,6 +27,8 @@ import ( "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest" "github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache" "github.com/agent-substrate/substrate/internal/ateinterceptors" + "github.com/agent-substrate/substrate/internal/localca" + "github.com/agent-substrate/substrate/internal/localjwtauthority" "github.com/agent-substrate/substrate/internal/objectstore/objectstoretest" "github.com/agent-substrate/substrate/internal/resources" "github.com/agent-substrate/substrate/internal/volume" @@ -184,8 +186,48 @@ func setupTestWithVolumePlugins(t *testing.T, ns string, plugins map[string]volu mockDriverName: mockPlugin, } } + + actorJWTAuthority, err := localjwtauthority.GenerateECDSAP256Authority("1") + if err != nil { + t.Fatalf("Error generating actor JWT authority: %v", err) + } + + actorJWTAuthorityPool := &localjwtauthority.ConcretePool{ + Authorities: []*localjwtauthority.Authority{ + actorJWTAuthority, + }, + ActiveForSigning: "1", + } + + actorCA, err := localca.GenerateCA("1", localca.KeyTypeECDSAP256, 365*24*time.Hour) + if err != nil { + t.Fatalf("Error generating actor CA: %v", err) + } + + actorCAPool := &localca.ConcretePool{ + CAs: []*localca.CA{ + actorCA, + }, + ActiveForSigning: "1", + } + objectStore := objectstoretest.New() - service := controlapi.NewRPCService(persistence, wc, sandboxConfigLister, csiDriverConfigLister, scLister, dialer, instruments, "", volPlugins, objectStore) + + service := controlapi.NewRPCService( + persistence, + wc, + sandboxConfigLister, + csiDriverConfigLister, + scLister, + dialer, + instruments, + "", + volPlugins, + objectStore, + "https://nonexistent-issuer.example", + actorJWTAuthorityPool, + actorCAPool, + ) // 5. Start REAL gRPC Server for ATE API grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor( diff --git a/cmd/ateapi/internal/controlapi/service.go b/cmd/ateapi/internal/controlapi/service.go index 2460b4638d..b433515586 100644 --- a/cmd/ateapi/internal/controlapi/service.go +++ b/cmd/ateapi/internal/controlapi/service.go @@ -20,6 +20,8 @@ import ( "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" "github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache" + "github.com/agent-substrate/substrate/internal/localca" + "github.com/agent-substrate/substrate/internal/localjwtauthority" "github.com/agent-substrate/substrate/internal/objectstore" "github.com/agent-substrate/substrate/internal/resources" "github.com/agent-substrate/substrate/internal/volume" @@ -48,6 +50,10 @@ type RPCService struct { mu sync.RWMutex volumePlugins map[string]volume.VolumePluginControlPlane objectStore objectstore.Store + + actorIdentityJWTIssuer string + actorIDJWTPool localjwtauthority.Pool + actorIDCAPool localca.Pool } var _ ateapipb.ControlServer = (*RPCService)(nil) @@ -76,18 +82,24 @@ func NewRPCService( egressGatewayAddress string, volumePlugins map[string]volume.VolumePluginControlPlane, objectStore objectstore.Store, + actorIdentityJWTIssuer string, + actorIDJWTPool localjwtauthority.Pool, + actorIDCAPool localca.Pool, ) *RPCService { impl := newServiceImpl(persistence, storageClassLister) s := &RPCService{ - impl: impl, - persistence: persistence, - workerCache: workerCache, - sandboxConfigLister: sandboxConfigLister, - csiDriverConfigLister: csiDriverConfigLister, - dialer: dialer, - instruments: instruments, - volumePlugins: volumePlugins, - objectStore: objectStore, + impl: impl, + persistence: persistence, + workerCache: workerCache, + sandboxConfigLister: sandboxConfigLister, + csiDriverConfigLister: csiDriverConfigLister, + dialer: dialer, + instruments: instruments, + volumePlugins: volumePlugins, + objectStore: objectStore, + actorIdentityJWTIssuer: actorIdentityJWTIssuer, + actorIDJWTPool: actorIDJWTPool, + actorIDCAPool: actorIDCAPool, } s.actorWorkflow = NewActorWorkflow(impl, workerCache, dialer, sandboxConfigLister, storageClassLister, instruments, egressGatewayAddress, s, objectStore) s.workerWorkflow = NewWorkerWorkflow(impl) diff --git a/cmd/ateapi/internal/controlapi/validate.go b/cmd/ateapi/internal/controlapi/validate.go index 7a7765641d..04eec6ce9f 100644 --- a/cmd/ateapi/internal/controlapi/validate.go +++ b/cmd/ateapi/internal/controlapi/validate.go @@ -86,7 +86,7 @@ func ValidateCustom_WorkerAssignment_WorkerPodIp(_ context.Context, _ operation. // a guardrail, applied here because maxLength does not support bytes fields. const maxCSRBytes = 16384 -func ValidateCustom_MintCertRequest_CertificateSigningRequest(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []byte) field.ErrorList { +func ValidateCustom_MintActorCertificateRequest_CertificateSigningRequest(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []byte) field.ErrorList { if len(value) > maxCSRBytes { return field.ErrorList{field.TooLong(fldPath, nil, maxCSRBytes)} } diff --git a/cmd/ateapi/internal/controlapi/zz_generated.validation.go b/cmd/ateapi/internal/controlapi/zz_generated.validation.go index 616c5c9af5..a0dbf1de77 100644 --- a/cmd/ateapi/internal/controlapi/zz_generated.validation.go +++ b/cmd/ateapi/internal/controlapi/zz_generated.validation.go @@ -4595,13 +4595,13 @@ func Validate_LocalSnapshotInfo( return errs } -// Validate_MintCertRequest validates an instance of MintCertRequest according +// Validate_MintActorCertificateRequest validates an instance of MintActorCertificateRequest according // to declarative validation rules in the API schema. -func Validate_MintCertRequest( +func Validate_MintActorCertificateRequest( ctx context.Context, op operation.Operation, fldPath *field.Path, - obj, oldObj *ateapipb.MintCertRequest) (errs field.ErrorList) { + obj, oldObj *ateapipb.MintActorCertificateRequest) (errs field.ErrorList) { - { // field ateapipb.MintCertRequest.Worker + { // field ateapipb.MintActorCertificateRequest.Actor fn := func( fldPath *field.Path, obj, oldObj *ateapipb.ObjectRef, @@ -4621,102 +4621,83 @@ func Validate_MintCertRequest( if earlyReturn { return // do not proceed } - func() { // cohort = "atespace" - earlyReturn := false - if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "atespace", - func(o *ateapipb.ObjectRef) *string { return &o.Atespace }, validate.DirectEqual, validate.ForbiddenValue).MarkBeta().MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) - earlyReturn = true - } - if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "atespace", - func(o *ateapipb.ObjectRef) *string { return &o.Atespace }, validate.DirectEqual, validate.OptionalValue).MarkBeta().MarkShortCircuit(); len(e) != 0 { - earlyReturn = true - } - if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "atespace", - func(o *ateapipb.ObjectRef) *string { return &o.Atespace }, validate.DirectEqual, validate.OptionalValue).MarkBeta().MarkShortCircuit(); len(e) != 0 { - earlyReturn = true - } - if earlyReturn { - return // do not proceed - } - }() // call the type's validation function errs = append(errs, Validate_ObjectRef(ctx, op, fldPath, obj, oldObj)...) return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.MintCertRequest) *ateapipb.ObjectRef { - return oldObj.Worker + func(oldObj *ateapipb.MintActorCertificateRequest) *ateapipb.ObjectRef { + return oldObj.Actor }) - errs = append(errs, fn(fldPath.Child("worker"), obj.Worker, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("actor"), obj.Actor, oldVal, oldObj != nil)...) } - { // field ateapipb.MintCertRequest.CertificateSigningRequest + { // field ateapipb.MintActorCertificateRequest.ActorUid fn := func( fldPath *field.Path, - obj, oldObj []byte, + obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { - if ateDeepEqual(obj, oldObj) { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { return nil } } // call field-attached validations earlyReturn := false - if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - // custom validation - if e := ValidateCustom_MintCertRequest_CertificateSigningRequest(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.MintCertRequest) []byte { - return oldObj.CertificateSigningRequest + func(oldObj *ateapipb.MintActorCertificateRequest) *string { + return &oldObj.ActorUid }) - errs = append(errs, fn(fldPath.Child("certificate_signing_request"), obj.CertificateSigningRequest, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("actor_uid"), &obj.ActorUid, oldVal, oldObj != nil)...) } - { // field ateapipb.MintCertRequest.ExpectedActorUid + { // field ateapipb.MintActorCertificateRequest.CertificateSigningRequest fn := func( fldPath *field.Path, - obj, oldObj *string, + obj, oldObj []byte, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { - if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + if ateDeepEqual(obj, oldObj) { return nil } } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + // custom validation + if e := ValidateCustom_MintActorCertificateRequest_CertificateSigningRequest(ctx, op, fldPath, obj, oldObj); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.MintCertRequest) *string { - return &oldObj.ExpectedActorUid + func(oldObj *ateapipb.MintActorCertificateRequest) []byte { + return oldObj.CertificateSigningRequest }) - errs = append(errs, fn(fldPath.Child("expected_actor_uid"), &obj.ExpectedActorUid, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("certificate_signing_request"), obj.CertificateSigningRequest, oldVal, oldObj != nil)...) } - { // field ateapipb.MintCertRequest.Purpose + { // field ateapipb.MintActorCertificateRequest.Purpose fn := func( fldPath *field.Path, obj, oldObj *ateapipb.ActorCertificatePurpose, @@ -4745,7 +4726,7 @@ func Validate_MintCertRequest( return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.MintCertRequest) *ateapipb.ActorCertificatePurpose { + func(oldObj *ateapipb.MintActorCertificateRequest) *ateapipb.ActorCertificatePurpose { return &oldObj.Purpose }) errs = append(errs, fn(fldPath.Child("purpose"), &obj.Purpose, oldVal, oldObj != nil)...) @@ -4754,16 +4735,16 @@ func Validate_MintCertRequest( return errs } -// Validate_MintJWTRequest validates an instance of MintJWTRequest according +// Validate_MintActorJWTRequest validates an instance of MintActorJWTRequest according // to declarative validation rules in the API schema. -func Validate_MintJWTRequest( +func Validate_MintActorJWTRequest( ctx context.Context, op operation.Operation, fldPath *field.Path, - obj, oldObj *ateapipb.MintJWTRequest) (errs field.ErrorList) { + obj, oldObj *ateapipb.MintActorJWTRequest) (errs field.ErrorList) { - { // field ateapipb.MintJWTRequest.Audience + { // field ateapipb.MintActorJWTRequest.Actor fn := func( fldPath *field.Path, - obj, oldObj []string, + obj, oldObj *ateapipb.ObjectRef, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { @@ -4773,37 +4754,25 @@ func Validate_MintJWTRequest( } // call field-attached validations earlyReturn := false - if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16).MarkShortCircuit(); len(e) != 0 { - errs = append(errs, e...) - earlyReturn = true - } - if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } if earlyReturn { return // do not proceed } - if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, - func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { - return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 512) - }); len(e) != 0 { - errs = append(errs, e...) - } - // lists with set semantics require unique values - if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { - errs = append(errs, e...) - } + // call the type's validation function + errs = append(errs, Validate_ObjectRef(ctx, op, fldPath, obj, oldObj)...) return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.MintJWTRequest) []string { - return oldObj.Audience + func(oldObj *ateapipb.MintActorJWTRequest) *ateapipb.ObjectRef { + return oldObj.Actor }) - errs = append(errs, fn(fldPath.Child("audience"), obj.Audience, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("actor"), obj.Actor, oldVal, oldObj != nil)...) } - { // field ateapipb.MintJWTRequest.Atespace + { // field ateapipb.MintActorJWTRequest.ActorUid fn := func( fldPath *field.Path, obj, oldObj *string, @@ -4823,79 +4792,59 @@ func Validate_MintJWTRequest( if earlyReturn { return // do not proceed } - if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.MintJWTRequest) *string { - return &oldObj.Atespace + func(oldObj *ateapipb.MintActorJWTRequest) *string { + return &oldObj.ActorUid }) - errs = append(errs, fn(fldPath.Child("atespace"), &obj.Atespace, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("actor_uid"), &obj.ActorUid, oldVal, oldObj != nil)...) } - { // field ateapipb.MintJWTRequest.ActorName + { // field ateapipb.MintActorJWTRequest.Audience fn := func( fldPath *field.Path, - obj, oldObj *string, + obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) { // don't revalidate unchanged data if oldValueCorrelated && op.Type == operation.Update { - if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + if ateDeepEqual(obj, oldObj) { return nil } } // call field-attached validations earlyReturn := false - if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 16).MarkShortCircuit(); len(e) != 0 { errs = append(errs, e...) earlyReturn = true } - if earlyReturn { - return // do not proceed - } - if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { errs = append(errs, e...) - } - return - } - oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.MintJWTRequest) *string { - return &oldObj.ActorName - }) - errs = append(errs, fn(fldPath.Child("actor_name"), &obj.ActorName, oldVal, oldObj != nil)...) - } - - { // field ateapipb.MintJWTRequest.ActorUid - fn := func( - fldPath *field.Path, - obj, oldObj *string, - oldValueCorrelated bool) (errs field.ErrorList) { - // don't revalidate unchanged data - if oldValueCorrelated && op.Type == operation.Update { - if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { - return nil - } - } - // call field-attached validations - earlyReturn := false - if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { earlyReturn = true } if earlyReturn { return // do not proceed } - if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 512) + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { errs = append(errs, e...) } return } oldVal := safe.Field(oldObj, - func(oldObj *ateapipb.MintJWTRequest) *string { - return &oldObj.ActorUid + func(oldObj *ateapipb.MintActorJWTRequest) []string { + return oldObj.Audience }) - errs = append(errs, fn(fldPath.Child("actor_uid"), &obj.ActorUid, oldVal, oldObj != nil)...) + errs = append(errs, fn(fldPath.Child("audience"), obj.Audience, oldVal, oldObj != nil)...) } return errs diff --git a/cmd/ateapi/main.go b/cmd/ateapi/main.go index e35af167cf..94948beeb8 100644 --- a/cmd/ateapi/main.go +++ b/cmd/ateapi/main.go @@ -28,7 +28,6 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/agent-substrate/substrate/cmd/ateapi/internal/actoridentity" "github.com/agent-substrate/substrate/cmd/ateapi/internal/controlapi" "github.com/agent-substrate/substrate/cmd/ateapi/internal/oidcjwt" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" @@ -198,11 +197,6 @@ func main() { volPlugins := make(map[string]volume.VolumePluginControlPlane) ateletDialer := controlapi.NewAteletDialer(workerPodInformer.GetIndexer(), ateletPodInformer.GetIndexer(), *ateletClientCredBundle, *podIdentityCACerts) - controlSrv := controlapi.NewRPCService(persistence, workerCache, sandboxConfigLister, csiDriverConfigLister, storageClassLister, ateletDialer, instruments, *egressGatewayAddress, volPlugins, objectStore) - - // Drive stored ActorTemplates through the golden actor flow. - templateReconciler := controlapi.NewActorTemplateReconciler(persistence, controlSrv) - templateReconciler.Start(shutdownCtx) actorIDCAPool, err := localca.NewRefreshingPool(*actorIDCAPoolFile) if err != nil { @@ -214,7 +208,25 @@ func main() { serverboot.Fatal(ctx, "while loading the Actor ID JWT authority pool", err) } - actorIdentitySrv := actoridentity.New(actorIdentityJWTIssuer, actorIDJWTAuthorityPool, actorIDCAPool, persistence, workerCache) + controlSrv := controlapi.NewRPCService( + persistence, + workerCache, + sandboxConfigLister, + csiDriverConfigLister, + storageClassLister, + ateletDialer, + instruments, + *egressGatewayAddress, + volPlugins, + objectStore, + actorIdentityJWTIssuer, + actorIDJWTAuthorityPool, + actorIDCAPool, + ) + + // Drive stored ActorTemplates through the golden actor flow. + templateReconciler := controlapi.NewActorTemplateReconciler(persistence, controlSrv) + templateReconciler.Start(shutdownCtx) lisCfg := &net.ListenConfig{} lis, err := lisCfg.Listen(ctx, "tcp", *listenAddr) @@ -248,7 +260,6 @@ func main() { ) reflection.Register(mux) ateapipb.RegisterControlServer(mux, controlSrv) - ateapipb.RegisterActorIdentityServer(mux, actorIdentitySrv) ateapipb.RegisterWorkerServiceServer(mux, workerservice.New(persistence)) readiness := &serverboot.Readiness{} diff --git a/cmd/atelet/credentialbroker.go b/cmd/atelet/ateomsupport.go similarity index 57% rename from cmd/atelet/credentialbroker.go rename to cmd/atelet/ateomsupport.go index 97eabb10bd..7f75db1cdf 100644 --- a/cmd/atelet/credentialbroker.go +++ b/cmd/atelet/ateomsupport.go @@ -18,6 +18,7 @@ import ( "context" "crypto/tls" "fmt" + "log/slog" "github.com/agent-substrate/substrate/internal/proto/ateletpb" "github.com/agent-substrate/substrate/internal/substratex509" @@ -28,26 +29,29 @@ import ( "google.golang.org/grpc/status" ) -type credentialBroker struct { - ateletpb.UnimplementedCredentialBrokerServer - // actorIdentityClient resolves the authenticated worker's current assignment - // and signs its actor certificate. - actorIdentityClient ateapipb.ActorIdentityClient +type ateomSupportServer struct { + ateletpb.UnimplementedAteomSupportServer + controlClient ateapipb.ControlClient + workers ateapipb.WorkerServiceClient } -func (b *credentialBroker) MintActorCertificate(ctx context.Context, req *ateletpb.MintActorCertificateRequest) (*ateletpb.MintActorCertificateResponse, error) { - // TODO: Before release, require the egress PEP to reject actor certificates - // whose ActorIdentity purpose is not atunnel. - // Worker identity comes only from the mTLS certificate. The expected actor - // UID is a stale-activation guard; ateapi derives the actor authoritatively. - workerIdentity, err := authenticatedWorkerIdentity(ctx) +func (b *ateomSupportServer) MintActorCertificate(ctx context.Context, req *ateletpb.MintActorCertificateRequest) (*ateletpb.MintActorCertificateResponse, error) { + // Check which ateom is calling. + _, err := authenticatedWorkerIdentity(ctx) if err != nil { return nil, err } - resp, err := b.actorIdentityClient.MintCert(ctx, &ateapipb.MintCertRequest{ - // Workers are global-scoped and named by their pod UID. - Worker: &ateapipb.ObjectRef{Name: workerIdentity.PodUID}, - ExpectedActorUid: req.GetExpectedActorUid(), + + // TODO(identity): Check that we believe that this ateom is running the + // requested actor? ate-api-server will further check that we (the atelet) + // are allowed to request a certificate for the actor. + + resp, err := b.controlClient.MintActorCertificate(ctx, &ateapipb.MintActorCertificateRequest{ + Actor: &ateapipb.ObjectRef{ + Atespace: req.GetActorAtespace(), + Name: req.GetActorName(), + }, + ActorUid: req.GetActorUid(), CertificateSigningRequest: req.GetCertificateSigningRequest(), Purpose: ateapipb.ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_ATUNNEL, }) @@ -90,3 +94,30 @@ func verifyClientOnSameNode(node *substratex509.PodIdentity) func(tls.Connection return nil } } + +// SetWorkerCapacity records what the calling worker says it has. +// +// It returns the control plane's error unwrapped so the caller retries: a +// worker reports once, so an accepted call is the only thing that puts +// capacity on the Worker, and a Worker record the syncer has not created yet +// is the ordinary reason for a first attempt to fail. +func (s *ateomSupportServer) SetWorkerCapacity(ctx context.Context, req *ateletpb.SetWorkerCapacityRequest) (*ateletpb.SetWorkerCapacityResponse, error) { + // Identity comes only from the mTLS certificate, never from the request: + // a worker can report its own capacity and no one else's. + workerIdentity, err := authenticatedWorkerIdentity(ctx) + if err != nil { + return nil, err + } + // Forwarded as reported: the worker speaks the vocabulary the control plane + // records, so there is nothing to translate. + if _, err := s.workers.SetWorkerCapacity(ctx, &ateapipb.SetWorkerCapacityRequest{ + // Workers are global-scoped and named by their pod UID. + Worker: &ateapipb.ObjectRef{Name: workerIdentity.PodUID}, + Capacity: req.GetCapacity(), + }); err != nil { + return nil, err + } + slog.InfoContext(ctx, "Recorded worker capacity", + slog.String("pod_uid", workerIdentity.PodUID), slog.Any("capacity", req.GetCapacity())) + return &ateletpb.SetWorkerCapacityResponse{}, nil +} diff --git a/cmd/atelet/workercapacity_test.go b/cmd/atelet/ateomsupport_test.go similarity index 62% rename from cmd/atelet/workercapacity_test.go rename to cmd/atelet/ateomsupport_test.go index af34b2f5dd..8b220640e1 100644 --- a/cmd/atelet/workercapacity_test.go +++ b/cmd/atelet/ateomsupport_test.go @@ -16,20 +16,75 @@ package main import ( "context" + "crypto/ed25519" + "crypto/rand" + "crypto/tls" + "crypto/x509" "errors" + "math/big" "testing" + "time" + "github.com/agent-substrate/substrate/internal/proto/ateletpb" + "github.com/agent-substrate/substrate/internal/resources" + "github.com/agent-substrate/substrate/internal/substratex509" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "github.com/google/go-cmp/cmp" "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" "google.golang.org/grpc/status" "google.golang.org/protobuf/testing/protocmp" - - "github.com/agent-substrate/substrate/internal/proto/ateletpb" - "github.com/agent-substrate/substrate/internal/resources" - "github.com/agent-substrate/substrate/pkg/proto/ateapipb" ) +func workerContext(t *testing.T, podUID string) context.Context { + t.Helper() + cert := workerCertificate(t, podUID, "node") + return peer.NewContext(context.Background(), &peer.Peer{AuthInfo: credentials.TLSInfo{State: tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}}}}) +} + +func TestVerifyClientOnSameNode(t *testing.T) { + state := tls.ConnectionState{PeerCertificates: []*x509.Certificate{workerCertificate(t, "worker-uid", "node-a")}} + nodeA := &substratex509.PodIdentity{NodeName: "node-a", NodeUID: "node-uid"} + if err := verifyClientOnSameNode(nodeA)(state); err != nil { + t.Fatalf("same-node worker rejected: %v", err) + } + if err := verifyClientOnSameNode(&substratex509.PodIdentity{NodeName: "node-b", NodeUID: "node-uid"})(state); err == nil { + t.Fatal("cross-node worker accepted") + } + if err := verifyClientOnSameNode(&substratex509.PodIdentity{NodeName: "node-a", NodeUID: "replacement-node"})(state); err == nil { + t.Fatal("replacement node accepted") + } +} + +func workerCertificate(t *testing.T, podUID, nodeName string) *x509.Certificate { + t.Helper() + _, key, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + template := &x509.Certificate{SerialNumber: big.NewInt(1), NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(time.Hour)} + if err := substratex509.AddPodIdentityToCertificate(&substratex509.PodIdentity{ + Namespace: "workers", ServiceAccountName: "default", ServiceAccountUID: "sa-uid", + PodName: "worker", PodUID: podUID, NodeName: nodeName, NodeUID: "node-uid", + }, template); err != nil { + t.Fatal(err) + } + der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) + if err != nil { + t.Fatal(err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + t.Fatal(err) + } + return cert +} + +// TODO: Use bufconn + an actual server implementation. +// +// https://google.github.io/styleguide/go/best-practices.html#use-real-transports type fakeWorkerService struct { ateapipb.WorkerServiceClient @@ -47,7 +102,7 @@ func (s *fakeWorkerService) SetWorkerCapacity(_ context.Context, in *ateapipb.Se func TestSetWorkerCapacityRecordsWhatTheWorkerSays(t *testing.T) { workers := &fakeWorkerService{} - svc := &workerCapacityService{workers: workers} + svc := &ateomSupportServer{workers: workers} ctx := workerContext(t, "pod-a") reported := &ateapipb.WorkerResources{ @@ -73,7 +128,7 @@ func TestSetWorkerCapacityRecordsWhatTheWorkerSays(t *testing.T) { func TestSetWorkerCapacityOmitsUndeterminedCompute(t *testing.T) { workers := &fakeWorkerService{} - svc := &workerCapacityService{workers: workers} + svc := &ateomSupportServer{workers: workers} ctx := workerContext(t, "pod-a") if _, err := svc.SetWorkerCapacity(ctx, &ateletpb.SetWorkerCapacityRequest{Capacity: &ateapipb.WorkerResources{Actors: 1}}); err != nil { @@ -87,7 +142,7 @@ func TestSetWorkerCapacityOmitsUndeterminedCompute(t *testing.T) { func TestSetWorkerCapacityRequiresACertificate(t *testing.T) { workers := &fakeWorkerService{} - svc := &workerCapacityService{workers: workers} + svc := &ateomSupportServer{workers: workers} // No peer identity: a worker may report only what its certificate proves // it is, so there is nothing to attribute this to. @@ -105,7 +160,7 @@ func TestSetWorkerCapacitySurfacesRejection(t *testing.T) { // it retries: it reports once, so a swallowed failure leaves the Worker // with no capacity forever. workers := &fakeWorkerService{err: errors.New("no such worker")} - svc := &workerCapacityService{workers: workers} + svc := &ateomSupportServer{workers: workers} ctx := workerContext(t, "pod-a") if _, err := svc.SetWorkerCapacity(ctx, &ateletpb.SetWorkerCapacityRequest{Capacity: &ateapipb.WorkerResources{Actors: 1}}); err == nil { diff --git a/cmd/atelet/credentialbroker_test.go b/cmd/atelet/credentialbroker_test.go deleted file mode 100644 index 53b3b50302..0000000000 --- a/cmd/atelet/credentialbroker_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2026 Google LLC -// -// 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 main - -import ( - "context" - "crypto/ed25519" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "math/big" - "testing" - "time" - - "github.com/agent-substrate/substrate/internal/proto/ateletpb" - "github.com/agent-substrate/substrate/internal/substratex509" - "github.com/agent-substrate/substrate/pkg/proto/ateapipb" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/peer" - "google.golang.org/protobuf/proto" -) - -type brokerIdentityClient struct { - ateapipb.ActorIdentityClient - request *ateapipb.MintCertRequest -} - -func (c *brokerIdentityClient) MintCert(_ context.Context, req *ateapipb.MintCertRequest, _ ...grpc.CallOption) (*ateapipb.MintCertResponse, error) { - c.request = req - return &ateapipb.MintCertResponse{ActorCertificates: [][]byte{{1, 2, 3}}}, nil -} - -func TestCredentialBrokerForwardsAuthenticatedWorkerIdentity(t *testing.T) { - identity := &brokerIdentityClient{} - broker := &credentialBroker{actorIdentityClient: identity} - csr := []byte{4, 5, 6} - resp, err := broker.MintActorCertificate(workerContext(t, "worker-uid"), &ateletpb.MintActorCertificateRequest{ - CertificateSigningRequest: csr, - ExpectedActorUid: "actor-uid", - }) - if err != nil { - t.Fatal(err) - } - if !proto.Equal(resp, &ateletpb.MintActorCertificateResponse{ActorCertificates: [][]byte{{1, 2, 3}}}) { - t.Fatalf("response = %+v", resp) - } - want := &ateapipb.MintCertRequest{Worker: &ateapipb.ObjectRef{Name: "worker-uid"}, ExpectedActorUid: "actor-uid", CertificateSigningRequest: csr, Purpose: ateapipb.ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_ATUNNEL} - if !proto.Equal(identity.request, want) { - t.Fatalf("MintCert request = %+v, want %+v", identity.request, want) - } -} - -func workerContext(t *testing.T, podUID string) context.Context { - t.Helper() - cert := workerCertificate(t, podUID, "node") - return peer.NewContext(context.Background(), &peer.Peer{AuthInfo: credentials.TLSInfo{State: tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}}}}) -} - -func TestVerifyClientOnSameNode(t *testing.T) { - state := tls.ConnectionState{PeerCertificates: []*x509.Certificate{workerCertificate(t, "worker-uid", "node-a")}} - nodeA := &substratex509.PodIdentity{NodeName: "node-a", NodeUID: "node-uid"} - if err := verifyClientOnSameNode(nodeA)(state); err != nil { - t.Fatalf("same-node worker rejected: %v", err) - } - if err := verifyClientOnSameNode(&substratex509.PodIdentity{NodeName: "node-b", NodeUID: "node-uid"})(state); err == nil { - t.Fatal("cross-node worker accepted") - } - if err := verifyClientOnSameNode(&substratex509.PodIdentity{NodeName: "node-a", NodeUID: "replacement-node"})(state); err == nil { - t.Fatal("replacement node accepted") - } -} - -func workerCertificate(t *testing.T, podUID, nodeName string) *x509.Certificate { - t.Helper() - _, key, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - template := &x509.Certificate{SerialNumber: big.NewInt(1), NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(time.Hour)} - if err := substratex509.AddPodIdentityToCertificate(&substratex509.PodIdentity{ - Namespace: "workers", ServiceAccountName: "default", ServiceAccountUID: "sa-uid", - PodName: "worker", PodUID: podUID, NodeName: nodeName, NodeUID: "node-uid", - }, template); err != nil { - t.Fatal(err) - } - der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) - if err != nil { - t.Fatal(err) - } - cert, err := x509.ParseCertificate(der) - if err != nil { - t.Fatal(err) - } - return cert -} diff --git a/cmd/atelet/main.go b/cmd/atelet/main.go index 0314faa82e..16f28f1946 100644 --- a/cmd/atelet/main.go +++ b/cmd/atelet/main.go @@ -347,28 +347,29 @@ func main() { if ateletIdentity == nil { serverboot.Fatal(ctx, "Failed to load atelet Pod identity", fmt.Errorf("credential bundle has no Pod identity")) } - brokerTLS := tlsCfg.Clone() - brokerTLS.VerifyConnection = verifyClientOnSameNode(ateletIdentity) - if err := os.Remove(ateompath.CredentialBrokerSocket); err != nil && !errors.Is(err, os.ErrNotExist) { + + ateomFacingTLS := tlsCfg.Clone() + ateomFacingTLS.VerifyConnection = verifyClientOnSameNode(ateletIdentity) + if err := os.Remove(ateompath.AteomSupportSocket); err != nil && !errors.Is(err, os.ErrNotExist) { serverboot.Fatal(ctx, "Failed to remove stale credential broker socket", err) } - brokerLis, err := net.Listen("unix", ateompath.CredentialBrokerSocket) + ateomFacingLis, err := net.Listen("unix", ateompath.AteomSupportSocket) if err != nil { serverboot.Fatal(ctx, "Failed to listen for credential broker", err) } - defer brokerLis.Close() - if err := os.Chmod(ateompath.CredentialBrokerSocket, 0o600); err != nil { + defer ateomFacingLis.Close() + if err := os.Chmod(ateompath.AteomSupportSocket, 0o600); err != nil { serverboot.Fatal(ctx, "Failed to restrict credential broker socket", err) } - brokerServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(brokerTLS))) - ateletpb.RegisterCredentialBrokerServer(brokerServer, &credentialBroker{ - actorIdentityClient: ateapipb.NewActorIdentityClient(ateapiConn), - }) - ateletpb.RegisterWorkerCapacityServer(brokerServer, &workerCapacityService{ - workers: ateapipb.NewWorkerServiceClient(ateapiConn), + + ateomFacingSrv := grpc.NewServer(grpc.Creds(credentials.NewTLS(ateomFacingTLS))) + + ateletpb.RegisterAteomSupportServer(ateomFacingSrv, &ateomSupportServer{ + controlClient: ateapipb.NewControlClient(ateapiConn), + workers: ateapipb.NewWorkerServiceClient(ateapiConn), }) go func() { - if err := brokerServer.Serve(brokerLis); err != nil { + if err := ateomFacingSrv.Serve(ateomFacingLis); err != nil { serverboot.Fatal(ctx, "Failed to serve credential broker", err) } }() diff --git a/cmd/atelet/workercapacity.go b/cmd/atelet/workercapacity.go deleted file mode 100644 index b53a102133..0000000000 --- a/cmd/atelet/workercapacity.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2026 Google LLC -// -// 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 main - -import ( - "context" - "log/slog" - - "github.com/agent-substrate/substrate/internal/proto/ateletpb" - "github.com/agent-substrate/substrate/pkg/proto/ateapipb" -) - -// workerCapacityService forwards a worker's own account of what it can supply -// to the control plane. The worker is the only thing that knows: the control -// plane sees a Pod, not what the runtime will actually give an actor. -type workerCapacityService struct { - ateletpb.UnimplementedWorkerCapacityServer - - workers ateapipb.WorkerServiceClient -} - -// SetWorkerCapacity records what the calling worker says it has. -// -// It returns the control plane's error unwrapped so the caller retries: a -// worker reports once, so an accepted call is the only thing that puts -// capacity on the Worker, and a Worker record the syncer has not created yet -// is the ordinary reason for a first attempt to fail. -func (s *workerCapacityService) SetWorkerCapacity(ctx context.Context, req *ateletpb.SetWorkerCapacityRequest) (*ateletpb.SetWorkerCapacityResponse, error) { - // Identity comes only from the mTLS certificate, never from the request: - // a worker can report its own capacity and no one else's. - workerIdentity, err := authenticatedWorkerIdentity(ctx) - if err != nil { - return nil, err - } - // Forwarded as reported: the worker speaks the vocabulary the control plane - // records, so there is nothing to translate. - if _, err := s.workers.SetWorkerCapacity(ctx, &ateapipb.SetWorkerCapacityRequest{ - // Workers are global-scoped and named by their pod UID. - Worker: &ateapipb.ObjectRef{Name: workerIdentity.PodUID}, - Capacity: req.GetCapacity(), - }); err != nil { - return nil, err - } - slog.InfoContext(ctx, "Recorded worker capacity", - slog.String("pod_uid", workerIdentity.PodUID), slog.Any("capacity", req.GetCapacity())) - return &ateletpb.SetWorkerCapacityResponse{}, nil -} diff --git a/cmd/ateom-gvisor/main.go b/cmd/ateom-gvisor/main.go index ff7835cc27..bcf6ea902f 100644 --- a/cmd/ateom-gvisor/main.go +++ b/cmd/ateom-gvisor/main.go @@ -244,7 +244,7 @@ func do(ctx context.Context) error { // that reaches here is a misconfiguration no restart-in-place will fix. go func() { err := ateomcapacity.Report(ctx, ateomcapacity.ReportConfig{ - SocketPath: ateompath.CredentialBrokerSocket, + SocketPath: ateompath.AteomSupportSocket, CredentialBundlePath: *workerCredentialBundle, TrustBundlePath: *podIdentityTrustBundle, }) @@ -1061,17 +1061,17 @@ func (s *AteomService) prepareActorEgress(ctx context.Context, actorUID string, return nil, fmt.Errorf("invalid egress gateway address %q: %w", gateway.GetAddress(), err) } certificateSource, err := atunnel.NewBrokerCertificateSource(atunnel.BrokerConfig{ - SocketPath: ateompath.CredentialBrokerSocket, + SocketPath: ateompath.AteomSupportSocket, CredentialBundlePath: s.workerCredentialBundlePath, TrustBundlePath: s.podIdentityTrustBundlePath, - ExpectedActorUID: actorUID, + ActorUID: actorUID, }) if err != nil { return nil, fmt.Errorf("while configuring actor certificate broker: %w", err) } // Mint before starting the workload so configured tunneled egress fails // closed. The source retains the private key for mTLS and renewal. - expiresAt, err := certificateSource.Mint(ctx) + expiresAt, err := certificateSource.MintAteomCertificate(ctx) if err != nil { return nil, fmt.Errorf("while obtaining actor certificate: %w", err) } diff --git a/cmd/ateom-microvm/main.go b/cmd/ateom-microvm/main.go index 36978e0b9c..e784298c7b 100644 --- a/cmd/ateom-microvm/main.go +++ b/cmd/ateom-microvm/main.go @@ -294,7 +294,7 @@ func do(ctx context.Context) error { // that reaches here is a misconfiguration no restart-in-place will fix. go func() { err := ateomcapacity.Report(ctx, ateomcapacity.ReportConfig{ - SocketPath: ateompath.CredentialBrokerSocket, + SocketPath: ateompath.AteomSupportSocket, CredentialBundlePath: *workerCredentialBundle, TrustBundlePath: *podIdentityTrustBundle, }) @@ -533,17 +533,18 @@ func (s *AteomService) prepareActorEgress(ctx context.Context, actorUID string, return nil, fmt.Errorf("invalid egress gateway address %q: %w", gateway.GetAddress(), err) } certificateSource, err := atunnel.NewBrokerCertificateSource(atunnel.BrokerConfig{ - SocketPath: ateompath.CredentialBrokerSocket, + SocketPath: ateompath.AteomSupportSocket, CredentialBundlePath: s.workerCredentialBundlePath, TrustBundlePath: s.podIdentityTrustBundlePath, - ExpectedActorUID: actorUID, + + ActorUID: actorUID, }) if err != nil { return nil, fmt.Errorf("while configuring actor certificate broker: %w", err) } // Mint before starting the workload so configured tunneled egress fails // closed. The source retains the private key for mTLS and renewal. - expiresAt, err := certificateSource.Mint(ctx) + expiresAt, err := certificateSource.MintAteomCertificate(ctx) if err != nil { return nil, fmt.Errorf("while obtaining actor certificate: %w", err) } diff --git a/internal/ateomcapacity/ateomcapacity.go b/internal/ateomcapacity/ateomcapacity.go index 731304d053..bfd05ae90f 100644 --- a/internal/ateomcapacity/ateomcapacity.go +++ b/internal/ateomcapacity/ateomcapacity.go @@ -151,6 +151,6 @@ func reportOnce(ctx context.Context, socketPath string, tlsConfig *tls.Config, c defer conn.Close() callCtx, cancel := context.WithTimeout(ctx, reportTimeout) defer cancel() - _, err = ateletpb.NewWorkerCapacityClient(conn).SetWorkerCapacity(callCtx, capacity) + _, err = ateletpb.NewAteomSupportClient(conn).SetWorkerCapacity(callCtx, capacity) return err } diff --git a/internal/ateompath/ateompath.go b/internal/ateompath/ateompath.go index 401dd313b6..2a18303c12 100644 --- a/internal/ateompath/ateompath.go +++ b/internal/ateompath/ateompath.go @@ -40,9 +40,9 @@ var ( // under it. ActorsDir = filepath.Join(BasePath, "actors") - // CredentialBrokerSocket is the node-local atelet socket used by atunnel + // AteomSupportSocket is the node-local atelet socket used by atunnel // to request credentials for the worker's current actor assignment. - CredentialBrokerSocket = filepath.Join(BasePath, "credential-broker.sock") + AteomSupportSocket = filepath.Join(BasePath, "ateom-support.sock") ) func RunSCBinaryPath(sha256 string) string { diff --git a/internal/atunnel/credential.go b/internal/atunnel/credential.go index 501ce75433..4986ed6b9f 100644 --- a/internal/atunnel/credential.go +++ b/internal/atunnel/credential.go @@ -34,10 +34,14 @@ import ( // BrokerCertificateSource owns atunnel's actor private key and obtains the // matching short-lived certificate from the node-local atelet. type BrokerCertificateSource struct { - socketPath string - expectedActorUID string - tlsConfig *tls.Config - privateKey *ecdsa.PrivateKey + socketPath string + + actorAtespace string + actorName string + actorUID string + + tlsConfig *tls.Config + privateKey *ecdsa.PrivateKey mu sync.RWMutex certificate *tls.Certificate @@ -52,18 +56,25 @@ type BrokerConfig struct { CredentialBundlePath string // TrustBundlePath verifies atelet's Pod certificate. TrustBundlePath string - // ExpectedActorUID prevents a mint started for an old activation from - // receiving the newly assigned actor's certificate. - ExpectedActorUID string + + // Which actor are we currently running? + ActorAtespace string + ActorName string + ActorUID string } // NewBrokerCertificateSource creates one actor key for this activation. The key // is reused across renewals and never leaves atunnel; only its CSR crosses the // credential broker socket. func NewBrokerCertificateSource(cfg BrokerConfig) (*BrokerCertificateSource, error) { - if cfg.SocketPath == "" || cfg.CredentialBundlePath == "" || cfg.TrustBundlePath == "" || cfg.ExpectedActorUID == "" { - return nil, fmt.Errorf("atunnel: credential broker socket, credentials, trust bundle, and expected actor UID are required") + if cfg.SocketPath == "" || cfg.CredentialBundlePath == "" || cfg.TrustBundlePath == "" { + return nil, fmt.Errorf("credential broker socket, credentials, trust bundle are required") + } + + if cfg.ActorAtespace == "" || cfg.ActorName == "" || cfg.ActorUID == "" { + return nil, fmt.Errorf("actor information is required") } + tlsConfig, err := ateletdial.TLSConfig(cfg.CredentialBundlePath, cfg.TrustBundlePath) if err != nil { return nil, fmt.Errorf("atunnel: %w", err) @@ -73,16 +84,27 @@ func NewBrokerCertificateSource(cfg BrokerConfig) (*BrokerCertificateSource, err return nil, fmt.Errorf("atunnel: generate actor private key: %w", err) } - return &BrokerCertificateSource{socketPath: cfg.SocketPath, expectedActorUID: cfg.ExpectedActorUID, tlsConfig: tlsConfig, privateKey: privateKey}, nil + return &BrokerCertificateSource{ + socketPath: cfg.SocketPath, + actorAtespace: cfg.ActorAtespace, + actorName: cfg.ActorName, + actorUID: cfg.ActorUID, + tlsConfig: tlsConfig, + privateKey: privateKey, + }, nil } -// Mint requests and installs a fresh certificate for the source's existing +// MintAteomCertificate requests and installs a fresh certificate for the source's existing // actor key. It returns the new expiry for renewal scheduling. -func (s *BrokerCertificateSource) Mint(ctx context.Context) (time.Time, error) { +func (s *BrokerCertificateSource) MintAteomCertificate(ctx context.Context) (time.Time, error) { csr, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{}, s.privateKey) if err != nil { return time.Time{}, fmt.Errorf("atunnel: create actor CSR: %w", err) } + + // TODO(identity): We should not be re-establishing a gRPC connection for + // every mint request. Do it once at atunnel startup. + // A fresh connection picks up rotated worker credentials and forces atelet's // current certificate and node identity to be verified for every mint. conn, err := ateletdial.Dial(s.socketPath, s.tlsConfig) @@ -90,9 +112,11 @@ func (s *BrokerCertificateSource) Mint(ctx context.Context) (time.Time, error) { return time.Time{}, err } defer conn.Close() - resp, err := ateletpb.NewCredentialBrokerClient(conn).MintActorCertificate(ctx, &ateletpb.MintActorCertificateRequest{ + resp, err := ateletpb.NewAteomSupportClient(conn).MintActorCertificate(ctx, &ateletpb.MintActorCertificateRequest{ + ActorAtespace: s.actorAtespace, + ActorName: s.actorName, + ActorUid: s.actorUID, CertificateSigningRequest: csr, - ExpectedActorUid: s.expectedActorUID, }) if err != nil { return time.Time{}, fmt.Errorf("atunnel: mint actor certificate: %w", err) @@ -122,7 +146,7 @@ func (s *BrokerCertificateSource) Mint(ctx context.Context) (time.Time, error) { if identity.Purpose != substratex509.ActorIdentityPurposeAtunnel { return time.Time{}, fmt.Errorf("atunnel: actor certificate is not scoped to atunnel") } - if identity.ActorUid != s.expectedActorUID { + if identity.ActorUid != s.actorUID { return time.Time{}, fmt.Errorf("atunnel: actor certificate is for an unexpected actor") } cert := &tls.Certificate{Certificate: chain, PrivateKey: s.privateKey, Leaf: leaf} diff --git a/internal/atunnel/credential_test.go b/internal/atunnel/credential_test.go index 057b6fe9e8..e0ac989a15 100644 --- a/internal/atunnel/credential_test.go +++ b/internal/atunnel/credential_test.go @@ -43,7 +43,7 @@ func TestBrokerCertificateSourceMintsAndReusesKey(t *testing.T) { defer cancel() for range 2 { - if _, err := source.Mint(ctx); err != nil { + if _, err := source.MintAteomCertificate(ctx); err != nil { t.Fatal(err) } } @@ -68,7 +68,7 @@ func TestBrokerCertificateSourceRejectsAteletOnDifferentNode(t *testing.T) { source, _ := newTestBrokerCertificateSource(t, testAteletIdentity("node-b"), time.Hour) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - if _, err := source.Mint(ctx); err == nil || !strings.Contains(err.Error(), "not on worker node") { + if _, err := source.MintAteomCertificate(ctx); err == nil || !strings.Contains(err.Error(), "not on worker node") { t.Fatalf("Mint() error = %v, want node identity rejection", err) } } @@ -77,7 +77,7 @@ func TestBrokerCertificateSourceRejectsExpiredCertificate(t *testing.T) { source, _ := newTestBrokerCertificateSource(t, testAteletIdentity("node-a"), -time.Minute) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - if _, err := source.Mint(ctx); err == nil || !strings.Contains(err.Error(), "invalid actor certificate lifetime") { + if _, err := source.MintAteomCertificate(ctx); err == nil || !strings.Contains(err.Error(), "invalid actor certificate lifetime") { t.Fatalf("Mint() error = %v, want expired certificate rejection", err) } } @@ -87,21 +87,21 @@ func TestBrokerCertificateSourceRejectsUnexpectedActor(t *testing.T) { broker.actorUID = "another-actor-uid" ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - if _, err := source.Mint(ctx); err == nil || !strings.Contains(err.Error(), "unexpected actor") { + if _, err := source.MintAteomCertificate(ctx); err == nil || !strings.Contains(err.Error(), "unexpected actor") { t.Fatalf("Mint() error = %v, want actor UID rejection", err) } } -type credentialBrokerStub struct { - ateletpb.UnimplementedCredentialBrokerServer +type ateomSupportStub struct { + ateletpb.UnimplementedAteomSupportServer ca *testCA lifetime time.Duration publicKeys chan []byte actorUID string } -func (s *credentialBrokerStub) MintActorCertificate(_ context.Context, req *ateletpb.MintActorCertificateRequest) (*ateletpb.MintActorCertificateResponse, error) { - if req.GetExpectedActorUid() != "actor-uid" { +func (s *ateomSupportStub) MintActorCertificate(_ context.Context, req *ateletpb.MintActorCertificateRequest) (*ateletpb.MintActorCertificateResponse, error) { + if req.GetActorUid() != "actor-uid" { return nil, status.Error(codes.FailedPrecondition, "unexpected actor UID") } csr, err := x509.ParseCertificateRequest(req.GetCertificateSigningRequest()) @@ -128,7 +128,7 @@ func (s *credentialBrokerStub) MintActorCertificate(_ context.Context, req *atel return &ateletpb.MintActorCertificateResponse{ActorCertificates: [][]byte{der}}, nil } -func newTestBrokerCertificateSource(t *testing.T, ateletIdentity *substratex509.PodIdentity, lifetime time.Duration) (*BrokerCertificateSource, *credentialBrokerStub) { +func newTestBrokerCertificateSource(t *testing.T, ateletIdentity *substratex509.PodIdentity, lifetime time.Duration) (*BrokerCertificateSource, *ateomSupportStub) { t.Helper() ca := newTestCA(t) workerCert := issueTestPodCertificate(t, ca, &substratex509.PodIdentity{ @@ -148,7 +148,7 @@ func newTestBrokerCertificateSource(t *testing.T, ateletIdentity *substratex509. trustPath := filepath.Join(dir, "trust.pem") writeCredentialBundle(t, credentialPath, workerCert) if err := os.WriteFile(trustPath, ca.certPEM, 0o600); err != nil { - t.Fatal(err) + t.Fatalf("While writing trust anchors: %v", err) } clientCAs := x509.NewCertPool() @@ -157,13 +157,13 @@ func newTestBrokerCertificateSource(t *testing.T, ateletIdentity *substratex509. // socket path limit on darwin, so the socket gets its own short dir. socketDir, err := os.MkdirTemp("", "atunnel") if err != nil { - t.Fatal(err) + t.Fatalf("Error creating temp dir: %v", err) } t.Cleanup(func() { _ = os.RemoveAll(socketDir) }) socketPath := filepath.Join(socketDir, "broker.sock") listener, err := net.Listen("unix", socketPath) if err != nil { - t.Fatal(err) + t.Fatalf("Error listening: %v", err) } server := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{ MinVersion: tls.VersionTLS13, @@ -171,8 +171,8 @@ func newTestBrokerCertificateSource(t *testing.T, ateletIdentity *substratex509. ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: clientCAs, }))) - broker := &credentialBrokerStub{ca: ca, lifetime: lifetime, publicKeys: make(chan []byte, 2), actorUID: "actor-uid"} - ateletpb.RegisterCredentialBrokerServer(server, broker) + broker := &ateomSupportStub{ca: ca, lifetime: lifetime, publicKeys: make(chan []byte, 2), actorUID: "actor-uid"} + ateletpb.RegisterAteomSupportServer(server, broker) go func() { _ = server.Serve(listener) }() t.Cleanup(func() { server.Stop() @@ -183,10 +183,12 @@ func newTestBrokerCertificateSource(t *testing.T, ateletIdentity *substratex509. SocketPath: socketPath, CredentialBundlePath: credentialPath, TrustBundlePath: trustPath, - ExpectedActorUID: "actor-uid", + ActorAtespace: "actor-atespace", + ActorName: "actor-name", + ActorUID: "actor-uid", }) if err != nil { - t.Fatal(err) + t.Fatalf("Error creating broker certificate source: %v", err) } return source, broker } diff --git a/internal/atunnel/egress.go b/internal/atunnel/egress.go index c42899c019..dbd317b7fc 100644 --- a/internal/atunnel/egress.go +++ b/internal/atunnel/egress.go @@ -35,7 +35,7 @@ type egressDialer interface { } type actorCertificateSource interface { - Mint(context.Context) (time.Time, error) + MintAteomCertificate(context.Context) (time.Time, error) } // OriginalDestination returns the address that a transparently intercepted @@ -142,7 +142,7 @@ func (e *Egress) renew(active *egressActivation, expiresAt time.Time) { slog.Time("expiredAt", expiresAt)) expired = true } - nextExpiry, err := active.certificateSource.Mint(active.ctx) + nextExpiry, err := active.certificateSource.MintAteomCertificate(active.ctx) if err != nil { code := status.Code(err) if code == codes.FailedPrecondition || code == codes.PermissionDenied { diff --git a/internal/atunnel/egress_test.go b/internal/atunnel/egress_test.go index 18440ec5f2..4463dd5548 100644 --- a/internal/atunnel/egress_test.go +++ b/internal/atunnel/egress_test.go @@ -353,7 +353,7 @@ type fakeActorCertificateSource struct { release <-chan struct{} } -func (s fakeActorCertificateSource) Mint(context.Context) (time.Time, error) { +func (s fakeActorCertificateSource) MintAteomCertificate(context.Context) (time.Time, error) { if s.calls != nil { s.calls.Add(1) } diff --git a/internal/proto/ateletpb/atelet.pb.go b/internal/proto/ateletpb/atelet.pb.go index 9bd0297ab6..4c4047ae8c 100644 --- a/internal/proto/ateletpb/atelet.pb.go +++ b/internal/proto/ateletpb/atelet.pb.go @@ -289,14 +289,17 @@ func (*SetWorkerCapacityResponse) Descriptor() ([]byte, []int) { type MintActorCertificateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` + // The actor for which the certificate should be issued. + ActorAtespace string `protobuf:"bytes,3,opt,name=actor_atespace,json=actorAtespace,proto3" json:"actor_atespace,omitempty"` + ActorName string `protobuf:"bytes,4,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` + // The UID of the actor --- used to guard against deletion and recreation of + // an actor with the same name. + ActorUid string `protobuf:"bytes,5,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` // DER-encoded PKCS #10 certificate signing request. Atunnel retains the // corresponding private key. CertificateSigningRequest []byte `protobuf:"bytes,1,opt,name=certificate_signing_request,json=certificateSigningRequest,proto3" json:"certificate_signing_request,omitempty"` - // Actor incarnation this activation expects. Ateapi resolves the actor from - // the authenticated worker and rejects the request if its UID differs. - ExpectedActorUid string `protobuf:"bytes,2,opt,name=expected_actor_uid,json=expectedActorUid,proto3" json:"expected_actor_uid,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *MintActorCertificateRequest) Reset() { @@ -329,20 +332,34 @@ func (*MintActorCertificateRequest) Descriptor() ([]byte, []int) { return file_atelet_proto_rawDescGZIP(), []int{2} } -func (x *MintActorCertificateRequest) GetCertificateSigningRequest() []byte { +func (x *MintActorCertificateRequest) GetActorAtespace() string { if x != nil { - return x.CertificateSigningRequest + return x.ActorAtespace } - return nil + return "" } -func (x *MintActorCertificateRequest) GetExpectedActorUid() string { +func (x *MintActorCertificateRequest) GetActorName() string { if x != nil { - return x.ExpectedActorUid + return x.ActorName } return "" } +func (x *MintActorCertificateRequest) GetActorUid() string { + if x != nil { + return x.ActorUid + } + return "" +} + +func (x *MintActorCertificateRequest) GetCertificateSigningRequest() []byte { + if x != nil { + return x.CertificateSigningRequest + } + return nil +} + type MintActorCertificateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // DER-encoded leaf followed by any intermediate certificates. @@ -2664,10 +2681,13 @@ const file_atelet_proto_rawDesc = "" + "\fatelet.proto\x12\x06atelet\x1a\x1fpkg/proto/ateapipb/ateapi.proto\"O\n" + "\x18SetWorkerCapacityRequest\x123\n" + "\bcapacity\x18\x01 \x01(\v2\x17.ateapi.WorkerResourcesR\bcapacity\"\x1b\n" + - "\x19SetWorkerCapacityResponse\"\x8b\x01\n" + - "\x1bMintActorCertificateRequest\x12>\n" + - "\x1bcertificate_signing_request\x18\x01 \x01(\fR\x19certificateSigningRequest\x12,\n" + - "\x12expected_actor_uid\x18\x02 \x01(\tR\x10expectedActorUid\"M\n" + + "\x19SetWorkerCapacityResponse\"\xc0\x01\n" + + "\x1bMintActorCertificateRequest\x12%\n" + + "\x0eactor_atespace\x18\x03 \x01(\tR\ractorAtespace\x12\x1d\n" + + "\n" + + "actor_name\x18\x04 \x01(\tR\tactorName\x12\x1b\n" + + "\tactor_uid\x18\x05 \x01(\tR\bactorUid\x12>\n" + + "\x1bcertificate_signing_request\x18\x01 \x01(\fR\x19certificateSigningRequest\"M\n" + "\x1cMintActorCertificateResponse\x12-\n" + "\x12actor_certificates\x18\x01 \x03(\fR\x11actorCertificates\"\xa6\x02\n" + "\x10TerminateRequest\x12(\n" + @@ -2853,10 +2873,9 @@ const file_atelet_proto_rawDesc = "" + "\x1aSNAPSHOT_SCOPE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13SNAPSHOT_SCOPE_FULL\x10\x01\x12\x17\n" + "\x13SNAPSHOT_SCOPE_DATA\x10\x02\x12!\n" + - "\x1dSNAPSHOT_SCOPE_DATA_ON_GOLDEN\x10\x032w\n" + - "\x10CredentialBroker\x12c\n" + - "\x14MintActorCertificate\x12#.atelet.MintActorCertificateRequest\x1a$.atelet.MintActorCertificateResponse\"\x002l\n" + - "\x0eWorkerCapacity\x12Z\n" + + "\x1dSNAPSHOT_SCOPE_DATA_ON_GOLDEN\x10\x032\xcf\x01\n" + + "\fAteomSupport\x12c\n" + + "\x14MintActorCertificate\x12#.atelet.MintActorCertificateRequest\x1a$.atelet.MintActorCertificateResponse\"\x00\x12Z\n" + "\x11SetWorkerCapacity\x12 .atelet.SetWorkerCapacityRequest\x1a!.atelet.SetWorkerCapacityResponse\"\x002\xf3\x02\n" + "\vAteomHerder\x120\n" + "\x03Run\x12\x12.atelet.RunRequest\x1a\x13.atelet.RunResponse\"\x00\x12E\n" + @@ -2968,15 +2987,15 @@ var file_atelet_proto_depIdxs = []int32{ 10, // 37: atelet.RestoreRequest.egress_gateway:type_name -> atelet.EgressGateway 11, // 38: atelet.ArchAssets.FilesEntry.value:type_name -> atelet.AssetFile 12, // 39: atelet.SandboxAssets.AssetsEntry.value:type_name -> atelet.ArchAssets - 5, // 40: atelet.CredentialBroker.MintActorCertificate:input_type -> atelet.MintActorCertificateRequest - 3, // 41: atelet.WorkerCapacity.SetWorkerCapacity:input_type -> atelet.SetWorkerCapacityRequest + 5, // 40: atelet.AteomSupport.MintActorCertificate:input_type -> atelet.MintActorCertificateRequest + 3, // 41: atelet.AteomSupport.SetWorkerCapacity:input_type -> atelet.SetWorkerCapacityRequest 9, // 42: atelet.AteomHerder.Run:input_type -> atelet.RunRequest 35, // 43: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest 39, // 44: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest 37, // 45: atelet.AteomHerder.UploadPausedCheckpoint:input_type -> atelet.UploadPausedCheckpointRequest 7, // 46: atelet.AteomHerder.Terminate:input_type -> atelet.TerminateRequest - 6, // 47: atelet.CredentialBroker.MintActorCertificate:output_type -> atelet.MintActorCertificateResponse - 4, // 48: atelet.WorkerCapacity.SetWorkerCapacity:output_type -> atelet.SetWorkerCapacityResponse + 6, // 47: atelet.AteomSupport.MintActorCertificate:output_type -> atelet.MintActorCertificateResponse + 4, // 48: atelet.AteomSupport.SetWorkerCapacity:output_type -> atelet.SetWorkerCapacityResponse 32, // 49: atelet.AteomHerder.Run:output_type -> atelet.RunResponse 36, // 50: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse 40, // 51: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse @@ -3021,7 +3040,7 @@ func file_atelet_proto_init() { NumEnums: 3, NumMessages: 41, NumExtensions: 0, - NumServices: 3, + NumServices: 2, }, GoTypes: file_atelet_proto_goTypes, DependencyIndexes: file_atelet_proto_depIdxs, diff --git a/internal/proto/ateletpb/atelet.proto b/internal/proto/ateletpb/atelet.proto index 869e83fccb..c4c79201dc 100644 --- a/internal/proto/ateletpb/atelet.proto +++ b/internal/proto/ateletpb/atelet.proto @@ -20,16 +20,19 @@ option go_package = "github.com/agent-substrate/substrate/internal/proto/ateletp import "pkg/proto/ateapipb/ateapi.proto"; -// CredentialBroker gives an authenticated worker its current actor credential. -service CredentialBroker { +// AteomSupport provides callouts for ateom. +// +// Called by individual ateom pods over a local connection, authenticated with +// ateom's k8s pod identity mTLS certificate. +service AteomSupport { + // Request an atunnel certificate for the given actor. + // + // TODO(identity): Rename to MintAtunnelCertificate, as distinct from + // MintActorCertificate (which would be used for certificates projected into + // the actor filesystem, when/if we support those). rpc MintActorCertificate(MintActorCertificateRequest) returns (MintActorCertificateResponse) {} -} -// WorkerCapacity is how a worker tells the node-local atelet what it can -// supply to the actors it hosts, for atelet to forward to the control plane's -// WorkerService.SetWorkerCapacity, which this mirrors. The worker is identified -// by its mTLS certificate, never by the request. -service WorkerCapacity { + // Report capacity and supply for this worker back to atelet. rpc SetWorkerCapacity(SetWorkerCapacityRequest) returns (SetWorkerCapacityResponse) {} } @@ -43,13 +46,17 @@ message SetWorkerCapacityResponse { } message MintActorCertificateRequest { + // The actor for which the certificate should be issued. + string actor_atespace = 3; + string actor_name = 4; + + // The UID of the actor --- used to guard against deletion and recreation of + // an actor with the same name. + string actor_uid = 5; + // DER-encoded PKCS #10 certificate signing request. Atunnel retains the // corresponding private key. bytes certificate_signing_request = 1; - - // Actor incarnation this activation expects. Ateapi resolves the actor from - // the authenticated worker and rejects the request if its UID differs. - string expected_actor_uid = 2; } message MintActorCertificateResponse { @@ -57,6 +64,10 @@ message MintActorCertificateResponse { repeated bytes actor_certificates = 1; } +// AteomHerder allows ate-apiserver to issue control calls to the atelet. +// +// Called by ate-api-server over cluster networking. ate-api-server +// authenticates with its k8s pod identity mTLS certificate. service AteomHerder { // Run tells atelet to create a new containerized workload from scratch on an // ateom. diff --git a/internal/proto/ateletpb/atelet_grpc.pb.go b/internal/proto/ateletpb/atelet_grpc.pb.go index b90612c2b8..1a51254c6c 100644 --- a/internal/proto/ateletpb/atelet_grpc.pb.go +++ b/internal/proto/ateletpb/atelet_grpc.pb.go @@ -33,217 +33,161 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - CredentialBroker_MintActorCertificate_FullMethodName = "/atelet.CredentialBroker/MintActorCertificate" + AteomSupport_MintActorCertificate_FullMethodName = "/atelet.AteomSupport/MintActorCertificate" + AteomSupport_SetWorkerCapacity_FullMethodName = "/atelet.AteomSupport/SetWorkerCapacity" ) -// CredentialBrokerClient is the client API for CredentialBroker service. +// AteomSupportClient is the client API for AteomSupport service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // -// CredentialBroker gives an authenticated worker its current actor credential. -type CredentialBrokerClient interface { +// AteomSupport provides callouts for ateom. +// +// Called by individual ateom pods over a local connection, authenticated with +// ateom's k8s pod identity mTLS certificate. +type AteomSupportClient interface { + // Request an atunnel certificate for the given actor. + // + // TODO(identity): Rename to MintAtunnelCertificate, as distinct from + // MintActorCertificate (which would be used for certificates projected into + // the actor filesystem, when/if we support those). MintActorCertificate(ctx context.Context, in *MintActorCertificateRequest, opts ...grpc.CallOption) (*MintActorCertificateResponse, error) + // Report capacity and supply for this worker back to atelet. + SetWorkerCapacity(ctx context.Context, in *SetWorkerCapacityRequest, opts ...grpc.CallOption) (*SetWorkerCapacityResponse, error) } -type credentialBrokerClient struct { +type ateomSupportClient struct { cc grpc.ClientConnInterface } -func NewCredentialBrokerClient(cc grpc.ClientConnInterface) CredentialBrokerClient { - return &credentialBrokerClient{cc} +func NewAteomSupportClient(cc grpc.ClientConnInterface) AteomSupportClient { + return &ateomSupportClient{cc} } -func (c *credentialBrokerClient) MintActorCertificate(ctx context.Context, in *MintActorCertificateRequest, opts ...grpc.CallOption) (*MintActorCertificateResponse, error) { +func (c *ateomSupportClient) MintActorCertificate(ctx context.Context, in *MintActorCertificateRequest, opts ...grpc.CallOption) (*MintActorCertificateResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(MintActorCertificateResponse) - err := c.cc.Invoke(ctx, CredentialBroker_MintActorCertificate_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, AteomSupport_MintActorCertificate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *ateomSupportClient) SetWorkerCapacity(ctx context.Context, in *SetWorkerCapacityRequest, opts ...grpc.CallOption) (*SetWorkerCapacityResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetWorkerCapacityResponse) + err := c.cc.Invoke(ctx, AteomSupport_SetWorkerCapacity_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -// CredentialBrokerServer is the server API for CredentialBroker service. -// All implementations must embed UnimplementedCredentialBrokerServer +// AteomSupportServer is the server API for AteomSupport service. +// All implementations must embed UnimplementedAteomSupportServer // for forward compatibility. // -// CredentialBroker gives an authenticated worker its current actor credential. -type CredentialBrokerServer interface { +// AteomSupport provides callouts for ateom. +// +// Called by individual ateom pods over a local connection, authenticated with +// ateom's k8s pod identity mTLS certificate. +type AteomSupportServer interface { + // Request an atunnel certificate for the given actor. + // + // TODO(identity): Rename to MintAtunnelCertificate, as distinct from + // MintActorCertificate (which would be used for certificates projected into + // the actor filesystem, when/if we support those). MintActorCertificate(context.Context, *MintActorCertificateRequest) (*MintActorCertificateResponse, error) - mustEmbedUnimplementedCredentialBrokerServer() + // Report capacity and supply for this worker back to atelet. + SetWorkerCapacity(context.Context, *SetWorkerCapacityRequest) (*SetWorkerCapacityResponse, error) + mustEmbedUnimplementedAteomSupportServer() } -// UnimplementedCredentialBrokerServer must be embedded to have +// UnimplementedAteomSupportServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. -type UnimplementedCredentialBrokerServer struct{} +type UnimplementedAteomSupportServer struct{} -func (UnimplementedCredentialBrokerServer) MintActorCertificate(context.Context, *MintActorCertificateRequest) (*MintActorCertificateResponse, error) { +func (UnimplementedAteomSupportServer) MintActorCertificate(context.Context, *MintActorCertificateRequest) (*MintActorCertificateResponse, error) { return nil, status.Error(codes.Unimplemented, "method MintActorCertificate not implemented") } -func (UnimplementedCredentialBrokerServer) mustEmbedUnimplementedCredentialBrokerServer() {} -func (UnimplementedCredentialBrokerServer) testEmbeddedByValue() {} +func (UnimplementedAteomSupportServer) SetWorkerCapacity(context.Context, *SetWorkerCapacityRequest) (*SetWorkerCapacityResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetWorkerCapacity not implemented") +} +func (UnimplementedAteomSupportServer) mustEmbedUnimplementedAteomSupportServer() {} +func (UnimplementedAteomSupportServer) testEmbeddedByValue() {} -// UnsafeCredentialBrokerServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to CredentialBrokerServer will +// UnsafeAteomSupportServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AteomSupportServer will // result in compilation errors. -type UnsafeCredentialBrokerServer interface { - mustEmbedUnimplementedCredentialBrokerServer() +type UnsafeAteomSupportServer interface { + mustEmbedUnimplementedAteomSupportServer() } -func RegisterCredentialBrokerServer(s grpc.ServiceRegistrar, srv CredentialBrokerServer) { - // If the following call panics, it indicates UnimplementedCredentialBrokerServer was +func RegisterAteomSupportServer(s grpc.ServiceRegistrar, srv AteomSupportServer) { + // If the following call panics, it indicates UnimplementedAteomSupportServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } - s.RegisterService(&CredentialBroker_ServiceDesc, srv) + s.RegisterService(&AteomSupport_ServiceDesc, srv) } -func _CredentialBroker_MintActorCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _AteomSupport_MintActorCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MintActorCertificateRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CredentialBrokerServer).MintActorCertificate(ctx, in) + return srv.(AteomSupportServer).MintActorCertificate(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: CredentialBroker_MintActorCertificate_FullMethodName, + FullMethod: AteomSupport_MintActorCertificate_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CredentialBrokerServer).MintActorCertificate(ctx, req.(*MintActorCertificateRequest)) + return srv.(AteomSupportServer).MintActorCertificate(ctx, req.(*MintActorCertificateRequest)) } return interceptor(ctx, in, info, handler) } -// CredentialBroker_ServiceDesc is the grpc.ServiceDesc for CredentialBroker service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var CredentialBroker_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "atelet.CredentialBroker", - HandlerType: (*CredentialBrokerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "MintActorCertificate", - Handler: _CredentialBroker_MintActorCertificate_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "atelet.proto", -} - -const ( - WorkerCapacity_SetWorkerCapacity_FullMethodName = "/atelet.WorkerCapacity/SetWorkerCapacity" -) - -// WorkerCapacityClient is the client API for WorkerCapacity service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -// -// WorkerCapacity is how a worker tells the node-local atelet what it can -// supply to the actors it hosts, for atelet to forward to the control plane's -// WorkerService.SetWorkerCapacity, which this mirrors. The worker is identified -// by its mTLS certificate, never by the request. -type WorkerCapacityClient interface { - SetWorkerCapacity(ctx context.Context, in *SetWorkerCapacityRequest, opts ...grpc.CallOption) (*SetWorkerCapacityResponse, error) -} - -type workerCapacityClient struct { - cc grpc.ClientConnInterface -} - -func NewWorkerCapacityClient(cc grpc.ClientConnInterface) WorkerCapacityClient { - return &workerCapacityClient{cc} -} - -func (c *workerCapacityClient) SetWorkerCapacity(ctx context.Context, in *SetWorkerCapacityRequest, opts ...grpc.CallOption) (*SetWorkerCapacityResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(SetWorkerCapacityResponse) - err := c.cc.Invoke(ctx, WorkerCapacity_SetWorkerCapacity_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// WorkerCapacityServer is the server API for WorkerCapacity service. -// All implementations must embed UnimplementedWorkerCapacityServer -// for forward compatibility. -// -// WorkerCapacity is how a worker tells the node-local atelet what it can -// supply to the actors it hosts, for atelet to forward to the control plane's -// WorkerService.SetWorkerCapacity, which this mirrors. The worker is identified -// by its mTLS certificate, never by the request. -type WorkerCapacityServer interface { - SetWorkerCapacity(context.Context, *SetWorkerCapacityRequest) (*SetWorkerCapacityResponse, error) - mustEmbedUnimplementedWorkerCapacityServer() -} - -// UnimplementedWorkerCapacityServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedWorkerCapacityServer struct{} - -func (UnimplementedWorkerCapacityServer) SetWorkerCapacity(context.Context, *SetWorkerCapacityRequest) (*SetWorkerCapacityResponse, error) { - return nil, status.Error(codes.Unimplemented, "method SetWorkerCapacity not implemented") -} -func (UnimplementedWorkerCapacityServer) mustEmbedUnimplementedWorkerCapacityServer() {} -func (UnimplementedWorkerCapacityServer) testEmbeddedByValue() {} - -// UnsafeWorkerCapacityServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to WorkerCapacityServer will -// result in compilation errors. -type UnsafeWorkerCapacityServer interface { - mustEmbedUnimplementedWorkerCapacityServer() -} - -func RegisterWorkerCapacityServer(s grpc.ServiceRegistrar, srv WorkerCapacityServer) { - // If the following call panics, it indicates UnimplementedWorkerCapacityServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&WorkerCapacity_ServiceDesc, srv) -} - -func _WorkerCapacity_SetWorkerCapacity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _AteomSupport_SetWorkerCapacity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SetWorkerCapacityRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(WorkerCapacityServer).SetWorkerCapacity(ctx, in) + return srv.(AteomSupportServer).SetWorkerCapacity(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: WorkerCapacity_SetWorkerCapacity_FullMethodName, + FullMethod: AteomSupport_SetWorkerCapacity_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WorkerCapacityServer).SetWorkerCapacity(ctx, req.(*SetWorkerCapacityRequest)) + return srv.(AteomSupportServer).SetWorkerCapacity(ctx, req.(*SetWorkerCapacityRequest)) } return interceptor(ctx, in, info, handler) } -// WorkerCapacity_ServiceDesc is the grpc.ServiceDesc for WorkerCapacity service. +// AteomSupport_ServiceDesc is the grpc.ServiceDesc for AteomSupport service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) -var WorkerCapacity_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "atelet.WorkerCapacity", - HandlerType: (*WorkerCapacityServer)(nil), +var AteomSupport_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "atelet.AteomSupport", + HandlerType: (*AteomSupportServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "MintActorCertificate", + Handler: _AteomSupport_MintActorCertificate_Handler, + }, { MethodName: "SetWorkerCapacity", - Handler: _WorkerCapacity_SetWorkerCapacity_Handler, + Handler: _AteomSupport_SetWorkerCapacity_Handler, }, }, Streams: []grpc.StreamDesc{}, @@ -261,6 +205,11 @@ const ( // AteomHerderClient is the client API for AteomHerder service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// AteomHerder allows ate-apiserver to issue control calls to the atelet. +// +// Called by ate-api-server over cluster networking. ate-api-server +// authenticates with its k8s pod identity mTLS certificate. type AteomHerderClient interface { // Run tells atelet to create a new containerized workload from scratch on an // ateom. @@ -342,6 +291,11 @@ func (c *ateomHerderClient) Terminate(ctx context.Context, in *TerminateRequest, // AteomHerderServer is the server API for AteomHerder service. // All implementations must embed UnimplementedAteomHerderServer // for forward compatibility. +// +// AteomHerder allows ate-apiserver to issue control calls to the atelet. +// +// Called by ate-api-server over cluster networking. ate-api-server +// authenticates with its k8s pod identity mTLS certificate. type AteomHerderServer interface { // Run tells atelet to create a new containerized workload from scratch on an // ateom. diff --git a/pkg/proto/ateapipb/ateapi.pb.go b/pkg/proto/ateapipb/ateapi.pb.go index 13db70e1fb..063b945335 100644 --- a/pkg/proto/ateapipb/ateapi.pb.go +++ b/pkg/proto/ateapipb/ateapi.pb.go @@ -368,100 +368,100 @@ func (ActorMetadataField) EnumDescriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{5} } -type WorkerState int32 +type ActorCertificatePurpose int32 const ( - WorkerState_WORKER_STATE_UNSPECIFIED WorkerState = 0 - // Ready; schedulable. - WorkerState_WORKER_STATE_ACTIVE WorkerState = 1 - // Pod terminating. Not schedulable. - WorkerState_WORKER_STATE_DRAINING WorkerState = 2 // Keep this in sync with WorkerStatus.state's maximum. + ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED ActorCertificatePurpose = 0 + ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_ATUNNEL ActorCertificatePurpose = 1 // Keep this in sync with MintCertRequest.purpose's maximum. ) -// Enum value maps for WorkerState. +// Enum value maps for ActorCertificatePurpose. var ( - WorkerState_name = map[int32]string{ - 0: "WORKER_STATE_UNSPECIFIED", - 1: "WORKER_STATE_ACTIVE", - 2: "WORKER_STATE_DRAINING", + ActorCertificatePurpose_name = map[int32]string{ + 0: "ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED", + 1: "ACTOR_CERTIFICATE_PURPOSE_ATUNNEL", } - WorkerState_value = map[string]int32{ - "WORKER_STATE_UNSPECIFIED": 0, - "WORKER_STATE_ACTIVE": 1, - "WORKER_STATE_DRAINING": 2, + ActorCertificatePurpose_value = map[string]int32{ + "ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED": 0, + "ACTOR_CERTIFICATE_PURPOSE_ATUNNEL": 1, } ) -func (x WorkerState) Enum() *WorkerState { - p := new(WorkerState) +func (x ActorCertificatePurpose) Enum() *ActorCertificatePurpose { + p := new(ActorCertificatePurpose) *p = x return p } -func (x WorkerState) String() string { +func (x ActorCertificatePurpose) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (WorkerState) Descriptor() protoreflect.EnumDescriptor { +func (ActorCertificatePurpose) Descriptor() protoreflect.EnumDescriptor { return file_ateapi_proto_enumTypes[6].Descriptor() } -func (WorkerState) Type() protoreflect.EnumType { +func (ActorCertificatePurpose) Type() protoreflect.EnumType { return &file_ateapi_proto_enumTypes[6] } -func (x WorkerState) Number() protoreflect.EnumNumber { +func (x ActorCertificatePurpose) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } -// Deprecated: Use WorkerState.Descriptor instead. -func (WorkerState) EnumDescriptor() ([]byte, []int) { +// Deprecated: Use ActorCertificatePurpose.Descriptor instead. +func (ActorCertificatePurpose) EnumDescriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{6} } -type ActorCertificatePurpose int32 +type WorkerState int32 const ( - ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED ActorCertificatePurpose = 0 - ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_ATUNNEL ActorCertificatePurpose = 1 // Keep this in sync with MintCertRequest.purpose's maximum. + WorkerState_WORKER_STATE_UNSPECIFIED WorkerState = 0 + // Ready; schedulable. + WorkerState_WORKER_STATE_ACTIVE WorkerState = 1 + // Pod terminating. Not schedulable. + WorkerState_WORKER_STATE_DRAINING WorkerState = 2 // Keep this in sync with WorkerStatus.state's maximum. ) -// Enum value maps for ActorCertificatePurpose. +// Enum value maps for WorkerState. var ( - ActorCertificatePurpose_name = map[int32]string{ - 0: "ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED", - 1: "ACTOR_CERTIFICATE_PURPOSE_ATUNNEL", + WorkerState_name = map[int32]string{ + 0: "WORKER_STATE_UNSPECIFIED", + 1: "WORKER_STATE_ACTIVE", + 2: "WORKER_STATE_DRAINING", } - ActorCertificatePurpose_value = map[string]int32{ - "ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED": 0, - "ACTOR_CERTIFICATE_PURPOSE_ATUNNEL": 1, + WorkerState_value = map[string]int32{ + "WORKER_STATE_UNSPECIFIED": 0, + "WORKER_STATE_ACTIVE": 1, + "WORKER_STATE_DRAINING": 2, } ) -func (x ActorCertificatePurpose) Enum() *ActorCertificatePurpose { - p := new(ActorCertificatePurpose) +func (x WorkerState) Enum() *WorkerState { + p := new(WorkerState) *p = x return p } -func (x ActorCertificatePurpose) String() string { +func (x WorkerState) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (ActorCertificatePurpose) Descriptor() protoreflect.EnumDescriptor { +func (WorkerState) Descriptor() protoreflect.EnumDescriptor { return file_ateapi_proto_enumTypes[7].Descriptor() } -func (ActorCertificatePurpose) Type() protoreflect.EnumType { +func (WorkerState) Type() protoreflect.EnumType { return &file_ateapi_proto_enumTypes[7] } -func (x ActorCertificatePurpose) Number() protoreflect.EnumNumber { +func (x WorkerState) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } -// Deprecated: Use ActorCertificatePurpose.Descriptor instead. -func (ActorCertificatePurpose) EnumDescriptor() ([]byte, []int) { +// Deprecated: Use WorkerState.Descriptor instead. +func (WorkerState) EnumDescriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{7} } @@ -4944,44 +4944,46 @@ func (x *GetTagRequest) GetTag() *ObjectRef { return nil } -type ListTagsRequest struct { +type MintActorJWTRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // The atespace to list tags from. Empty lists across all atespaces. + // The actor for which the JWT should be issued. // - // +k8s:optional - // +k8s:format=k8s-short-name - Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` - // Requested page size; the server may return fewer, or occasionally - // slightly more. If unspecified, defaults to a server-chosen value; - // values above 1000 are coerced to 1000. + // Must be a valid actor that currently exists according to the actor store. // - // +k8s:optional - // +k8s:minimum=1 - PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Pagination token from a previous ListTags response. - // Omit or leave empty for the first request. + // +k8s:required + Actor *ObjectRef `protobuf:"bytes,5,opt,name=actor,proto3" json:"actor,omitempty"` + // The UID of the actor --- used to guard against deletion and recreation of + // an actor with the same name. // - // +k8s:optional - // +k8s:maxLength=256 - PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // +k8s:required + // +k8s:format=k8s-uuid + ActorUid string `protobuf:"bytes,7,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + // The audiences the minted JWT is bound to. Tokens are only issued with + // audience bindings, so at least one is required. + // + // +k8s:required + // +k8s:maxItems=16 # guardrail; tokens realistically bind a handful of audiences + // +k8s:listType=set + // +k8s:eachVal=+k8s:maxLength=512 # audiences are caller-defined URIs; bound only + Audience []string `protobuf:"bytes,1,rep,name=audience,proto3" json:"audience,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListTagsRequest) Reset() { - *x = ListTagsRequest{} +func (x *MintActorJWTRequest) Reset() { + *x = MintActorJWTRequest{} mi := &file_ateapi_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListTagsRequest) String() string { +func (x *MintActorJWTRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListTagsRequest) ProtoMessage() {} +func (*MintActorJWTRequest) ProtoMessage() {} -func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { +func (x *MintActorJWTRequest) ProtoReflect() protoreflect.Message { mi := &file_ateapi_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4993,54 +4995,72 @@ func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListTagsRequest.ProtoReflect.Descriptor instead. -func (*ListTagsRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use MintActorJWTRequest.ProtoReflect.Descriptor instead. +func (*MintActorJWTRequest) Descriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{67} } -func (x *ListTagsRequest) GetAtespace() string { +func (x *MintActorJWTRequest) GetActor() *ObjectRef { if x != nil { - return x.Atespace + return x.Actor } - return "" + return nil } -func (x *ListTagsRequest) GetPageSize() int32 { +func (x *MintActorJWTRequest) GetActorUid() string { if x != nil { - return x.PageSize + return x.ActorUid } - return 0 + return "" } -func (x *ListTagsRequest) GetPageToken() string { +func (x *MintActorJWTRequest) GetAudience() []string { if x != nil { - return x.PageToken + return x.Audience } - return "" + return nil } -type ListTagsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Tags []*Tag `protobuf:"bytes,1,rep,name=tags,proto3" json:"tags,omitempty"` - NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +// TODO: check why k8s do ":" and not "/" as a seprator for the Subject format +// TODO: whats the right format for the subject? kubernetes follow "system:serviceaccount::". +type MintActorJWTResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Actor JWT. An OIDC Discovery-compatible JWT + // + // Claims: + // + // * iss: Issuer - a valid URL where a relying party can fetch the OIDC + // discovery documents. + // * sub: Subject - a string expressing the identity carried in the + // credential. Format + // `atespaces:${atespace}:actors:${actorname}`. + // * aud: Audience - a string identifying the service this token will be used + // to authenticate to. + // * nbf: Not Before - a numeric unix timestamp + // * exp: Expiration - a numeric unix timestamp + // * iat: Issued At - a numeric unix timestamp + // * `ate.dev`: Ate/Substrate Extension - JSON object + // * atespace: (string) The atespace the actor belongs to + // * actorName: (string) The actor's name, unique within its atespace + ActorJwt string `protobuf:"bytes,1,opt,name=actor_jwt,json=actorJwt,proto3" json:"actor_jwt,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListTagsResponse) Reset() { - *x = ListTagsResponse{} +func (x *MintActorJWTResponse) Reset() { + *x = MintActorJWTResponse{} mi := &file_ateapi_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListTagsResponse) String() string { +func (x *MintActorJWTResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListTagsResponse) ProtoMessage() {} +func (*MintActorJWTResponse) ProtoMessage() {} -func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { +func (x *MintActorJWTResponse) ProtoReflect() protoreflect.Message { mi := &file_ateapi_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5052,65 +5072,61 @@ func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListTagsResponse.ProtoReflect.Descriptor instead. -func (*ListTagsResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use MintActorJWTResponse.ProtoReflect.Descriptor instead. +func (*MintActorJWTResponse) Descriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{68} } -func (x *ListTagsResponse) GetTags() []*Tag { - if x != nil { - return x.Tags - } - return nil -} - -func (x *ListTagsResponse) GetNextPageToken() string { +func (x *MintActorJWTResponse) GetActorJwt() string { if x != nil { - return x.NextPageToken + return x.ActorJwt } return "" } -// Request to tag the external snapshot a suspended Actor holds. -// -// The tag captures whichever snapshot the Actor holds when the call runs. An -// Actor keeps no snapshot history — each suspend replaces the last — so a -// suspend that lands between taking a snapshot and tagging it moves what gets -// tagged. That race is inherent and accepted: the tag means "the Actor as of -// this call", not "the snapshot some earlier suspend returned". -// -// The tag is given its own copy of that snapshot, so it survives the Actor -// being suspended again or deleted. -// To retry a create that failed or timed out, delete the tag first, -// which collects whatever that attempt stranded, and create it again. -// -// +k8s:customValidation # metadata.atespace must match source_actor.atespace -type CreateTagRequest struct { +type MintActorCertificateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // The tag to create. metadata.atespace, metadata.name, scope and source_actor - // are honored; metadata.atespace must match source_actor's, and status is - // ignored on write. + // The actor for which the certificate should be issued. + // + // Must be a valid actor that currently exists according to the actor store. // // +k8s:required - Tag *Tag `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Actor *ObjectRef `protobuf:"bytes,6,opt,name=actor,proto3" json:"actor,omitempty"` + // The UID of the actor --- used to guard against deletion and recreation of + // an actor with the same name. + // + // +k8s:required + // +k8s:format=k8s-uuid + ActorUid string `protobuf:"bytes,7,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + // Request contains DER encoded bytes of a x509 certificate signing request. + // The signer will ignore the contents of the CSR except to extract the + // subject public key. + // + // +k8s:required + // +k8s:customValidation # size bound; maxLength is string-only + CertificateSigningRequest []byte `protobuf:"bytes,2,opt,name=certificate_signing_request,json=certificateSigningRequest,proto3" json:"certificate_signing_request,omitempty"` + // +k8s:required + // +k8s:minimum=1 + // +k8s:maximum=1 # keep this in sync with the ActorCertificatePurpose enum + Purpose ActorCertificatePurpose `protobuf:"varint,4,opt,name=purpose,proto3,enum=ateapi.ActorCertificatePurpose" json:"purpose,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateTagRequest) Reset() { - *x = CreateTagRequest{} +func (x *MintActorCertificateRequest) Reset() { + *x = MintActorCertificateRequest{} mi := &file_ateapi_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateTagRequest) String() string { +func (x *MintActorCertificateRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateTagRequest) ProtoMessage() {} +func (*MintActorCertificateRequest) ProtoMessage() {} -func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { +func (x *MintActorCertificateRequest) ProtoReflect() protoreflect.Message { mi := &file_ateapi_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5122,52 +5138,400 @@ func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateTagRequest.ProtoReflect.Descriptor instead. -func (*CreateTagRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use MintActorCertificateRequest.ProtoReflect.Descriptor instead. +func (*MintActorCertificateRequest) Descriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{69} } -func (x *CreateTagRequest) GetTag() *Tag { +func (x *MintActorCertificateRequest) GetActor() *ObjectRef { if x != nil { - return x.Tag + return x.Actor } return nil } -// Request to update mutable fields on an existing Tag. -// The tag keeps its address: the snapshot it points at cannot be changed. -type UpdateTagRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The tag to update. - // tag.metadata.atespace and tag.metadata.name identify which resource to - // update. - // tag.metadata.version and tag.metadata.uid are required preconditions - // - // +k8s:required - // +k8s:opaqueType # updates are handled in 2 steps, do not descend - // +k8s:subfield(metadata)=+k8s:required - // +k8s:customValidation # TODO: when we get nested subfields, require metadata.atespace - Tag *Tag `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *MintActorCertificateRequest) GetActorUid() string { + if x != nil { + return x.ActorUid + } + return "" } -func (x *UpdateTagRequest) Reset() { - *x = UpdateTagRequest{} - mi := &file_ateapi_proto_msgTypes[70] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *MintActorCertificateRequest) GetCertificateSigningRequest() []byte { + if x != nil { + return x.CertificateSigningRequest + } + return nil } -func (x *UpdateTagRequest) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *MintActorCertificateRequest) GetPurpose() ActorCertificatePurpose { + if x != nil { + return x.Purpose + } + return ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED } -func (*UpdateTagRequest) ProtoMessage() {} - -func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[70] - if x != nil { +type MintActorCertificateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Response contains a list of DER encoded certificates. The first entry is the + // leaf certificate, and any remaining entries are intermediates in + // leaf-to-root order. + ActorCertificates [][]byte `protobuf:"bytes,1,rep,name=actor_certificates,json=actorCertificates,proto3" json:"actor_certificates,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MintActorCertificateResponse) Reset() { + *x = MintActorCertificateResponse{} + mi := &file_ateapi_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MintActorCertificateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MintActorCertificateResponse) ProtoMessage() {} + +func (x *MintActorCertificateResponse) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[70] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MintActorCertificateResponse.ProtoReflect.Descriptor instead. +func (*MintActorCertificateResponse) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{70} +} + +func (x *MintActorCertificateResponse) GetActorCertificates() [][]byte { + if x != nil { + return x.ActorCertificates + } + return nil +} + +type GetActorSnapshotRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // +k8s:opaqueType + ActorSnapshot *ObjectRef `protobuf:"bytes,1,opt,name=actor_snapshot,json=actorSnapshot,proto3" json:"actor_snapshot,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetActorSnapshotRequest) Reset() { + *x = GetActorSnapshotRequest{} + mi := &file_ateapi_proto_msgTypes[71] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetActorSnapshotRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetActorSnapshotRequest) ProtoMessage() {} + +func (x *GetActorSnapshotRequest) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[71] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetActorSnapshotRequest.ProtoReflect.Descriptor instead. +func (*GetActorSnapshotRequest) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{71} +} + +func (x *GetActorSnapshotRequest) GetActorSnapshot() *ObjectRef { + if x != nil { + return x.ActorSnapshot + } + return nil +} + +type GetActorSnapshotTagRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // +k8s:opaqueType + ActorSnapshotTag *ObjectRef `protobuf:"bytes,1,opt,name=actor_snapshot_tag,json=actorSnapshotTag,proto3" json:"actor_snapshot_tag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetActorSnapshotTagRequest) Reset() { + *x = GetActorSnapshotTagRequest{} + mi := &file_ateapi_proto_msgTypes[72] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetActorSnapshotTagRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetActorSnapshotTagRequest) ProtoMessage() {} + +func (x *GetActorSnapshotTagRequest) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[72] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetActorSnapshotTagRequest.ProtoReflect.Descriptor instead. +func (*GetActorSnapshotTagRequest) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{72} +} + +func (x *GetActorSnapshotTagRequest) GetActorSnapshotTag() *ObjectRef { + if x != nil { + return x.ActorSnapshotTag + } + return nil +} + +type ListTagsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The atespace to list tags from. Empty lists across all atespaces. + // + // +k8s:optional + // +k8s:format=k8s-short-name + Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` + // Requested page size; the server may return fewer, or occasionally + // slightly more. If unspecified, defaults to a server-chosen value; + // values above 1000 are coerced to 1000. + // + // +k8s:optional + // +k8s:minimum=1 + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Pagination token from a previous ListTags response. + // Omit or leave empty for the first request. + // + // +k8s:optional + // +k8s:maxLength=256 + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTagsRequest) Reset() { + *x = ListTagsRequest{} + mi := &file_ateapi_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTagsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTagsRequest) ProtoMessage() {} + +func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[73] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTagsRequest.ProtoReflect.Descriptor instead. +func (*ListTagsRequest) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{73} +} + +func (x *ListTagsRequest) GetAtespace() string { + if x != nil { + return x.Atespace + } + return "" +} + +func (x *ListTagsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListTagsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type ListTagsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tags []*Tag `protobuf:"bytes,1,rep,name=tags,proto3" json:"tags,omitempty"` + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTagsResponse) Reset() { + *x = ListTagsResponse{} + mi := &file_ateapi_proto_msgTypes[74] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTagsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTagsResponse) ProtoMessage() {} + +func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[74] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTagsResponse.ProtoReflect.Descriptor instead. +func (*ListTagsResponse) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{74} +} + +func (x *ListTagsResponse) GetTags() []*Tag { + if x != nil { + return x.Tags + } + return nil +} + +func (x *ListTagsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +// Request to tag the external snapshot a suspended Actor holds. +// +// The tag captures whichever snapshot the Actor holds when the call runs. An +// Actor keeps no snapshot history — each suspend replaces the last — so a +// suspend that lands between taking a snapshot and tagging it moves what gets +// tagged. That race is inherent and accepted: the tag means "the Actor as of +// this call", not "the snapshot some earlier suspend returned". +// +// The tag is given its own copy of that snapshot, so it survives the Actor +// being suspended again or deleted. +// To retry a create that failed or timed out, delete the tag first, +// which collects whatever that attempt stranded, and create it again. +// +// +k8s:customValidation # metadata.atespace must match source_actor.atespace +type CreateTagRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The tag to create. metadata.atespace, metadata.name, scope and source_actor + // are honored; metadata.atespace must match source_actor's, and status is + // ignored on write. + // + // +k8s:required + Tag *Tag `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTagRequest) Reset() { + *x = CreateTagRequest{} + mi := &file_ateapi_proto_msgTypes[75] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTagRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTagRequest) ProtoMessage() {} + +func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[75] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTagRequest.ProtoReflect.Descriptor instead. +func (*CreateTagRequest) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{75} +} + +func (x *CreateTagRequest) GetTag() *Tag { + if x != nil { + return x.Tag + } + return nil +} + +// Request to update mutable fields on an existing Tag. +// The tag keeps its address: the snapshot it points at cannot be changed. +type UpdateTagRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The tag to update. + // tag.metadata.atespace and tag.metadata.name identify which resource to + // update. + // tag.metadata.version and tag.metadata.uid are required preconditions + // + // +k8s:required + // +k8s:opaqueType # updates are handled in 2 steps, do not descend + // +k8s:subfield(metadata)=+k8s:required + // +k8s:customValidation # TODO: when we get nested subfields, require metadata.atespace + Tag *Tag `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateTagRequest) Reset() { + *x = UpdateTagRequest{} + mi := &file_ateapi_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateTagRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTagRequest) ProtoMessage() {} + +func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[76] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5179,7 +5543,7 @@ func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateTagRequest.ProtoReflect.Descriptor instead. func (*UpdateTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{70} + return file_ateapi_proto_rawDescGZIP(), []int{76} } func (x *UpdateTagRequest) GetTag() *Tag { @@ -5200,7 +5564,7 @@ type DeleteTagRequest struct { func (x *DeleteTagRequest) Reset() { *x = DeleteTagRequest{} - mi := &file_ateapi_proto_msgTypes[71] + mi := &file_ateapi_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5212,7 +5576,7 @@ func (x *DeleteTagRequest) String() string { func (*DeleteTagRequest) ProtoMessage() {} func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[71] + mi := &file_ateapi_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5225,7 +5589,7 @@ func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteTagRequest.ProtoReflect.Descriptor instead. func (*DeleteTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{71} + return file_ateapi_proto_rawDescGZIP(), []int{77} } func (x *DeleteTagRequest) GetTag() *ObjectRef { @@ -5261,7 +5625,7 @@ type DeleteOptions struct { func (x *DeleteOptions) Reset() { *x = DeleteOptions{} - mi := &file_ateapi_proto_msgTypes[72] + mi := &file_ateapi_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5273,7 +5637,7 @@ func (x *DeleteOptions) String() string { func (*DeleteOptions) ProtoMessage() {} func (x *DeleteOptions) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[72] + mi := &file_ateapi_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5286,7 +5650,7 @@ func (x *DeleteOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteOptions.ProtoReflect.Descriptor instead. func (*DeleteOptions) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{72} + return file_ateapi_proto_rawDescGZIP(), []int{78} } func (x *DeleteOptions) GetVersion() int64 { @@ -5330,7 +5694,7 @@ type ListWorkerActorAssignmentsRequest struct { func (x *ListWorkerActorAssignmentsRequest) Reset() { *x = ListWorkerActorAssignmentsRequest{} - mi := &file_ateapi_proto_msgTypes[73] + mi := &file_ateapi_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5342,7 +5706,7 @@ func (x *ListWorkerActorAssignmentsRequest) String() string { func (*ListWorkerActorAssignmentsRequest) ProtoMessage() {} func (x *ListWorkerActorAssignmentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[73] + mi := &file_ateapi_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5355,7 +5719,7 @@ func (x *ListWorkerActorAssignmentsRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ListWorkerActorAssignmentsRequest.ProtoReflect.Descriptor instead. func (*ListWorkerActorAssignmentsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{73} + return file_ateapi_proto_rawDescGZIP(), []int{79} } func (x *ListWorkerActorAssignmentsRequest) GetWorker() *ObjectRef { @@ -5392,7 +5756,7 @@ type ListWorkerActorAssignmentsResponse struct { func (x *ListWorkerActorAssignmentsResponse) Reset() { *x = ListWorkerActorAssignmentsResponse{} - mi := &file_ateapi_proto_msgTypes[74] + mi := &file_ateapi_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5404,7 +5768,7 @@ func (x *ListWorkerActorAssignmentsResponse) String() string { func (*ListWorkerActorAssignmentsResponse) ProtoMessage() {} func (x *ListWorkerActorAssignmentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[74] + mi := &file_ateapi_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5417,7 +5781,7 @@ func (x *ListWorkerActorAssignmentsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListWorkerActorAssignmentsResponse.ProtoReflect.Descriptor instead. func (*ListWorkerActorAssignmentsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{74} + return file_ateapi_proto_rawDescGZIP(), []int{80} } func (x *ListWorkerActorAssignmentsResponse) GetActorAssignments() []*ActorAssignment { @@ -5455,7 +5819,7 @@ type ListWorkersRequest struct { func (x *ListWorkersRequest) Reset() { *x = ListWorkersRequest{} - mi := &file_ateapi_proto_msgTypes[75] + mi := &file_ateapi_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5467,7 +5831,7 @@ func (x *ListWorkersRequest) String() string { func (*ListWorkersRequest) ProtoMessage() {} func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[75] + mi := &file_ateapi_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5480,7 +5844,7 @@ func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersRequest.ProtoReflect.Descriptor instead. func (*ListWorkersRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{75} + return file_ateapi_proto_rawDescGZIP(), []int{81} } func (x *ListWorkersRequest) GetPageSize() int32 { @@ -5509,7 +5873,7 @@ type ListWorkersResponse struct { func (x *ListWorkersResponse) Reset() { *x = ListWorkersResponse{} - mi := &file_ateapi_proto_msgTypes[76] + mi := &file_ateapi_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5521,7 +5885,7 @@ func (x *ListWorkersResponse) String() string { func (*ListWorkersResponse) ProtoMessage() {} func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[76] + mi := &file_ateapi_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5534,7 +5898,7 @@ func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersResponse.ProtoReflect.Descriptor instead. func (*ListWorkersResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{76} + return file_ateapi_proto_rawDescGZIP(), []int{82} } func (x *ListWorkersResponse) GetWorkers() []*Worker { @@ -5564,7 +5928,7 @@ type GetWorkerRequest struct { func (x *GetWorkerRequest) Reset() { *x = GetWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[77] + mi := &file_ateapi_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5576,7 +5940,7 @@ func (x *GetWorkerRequest) String() string { func (*GetWorkerRequest) ProtoMessage() {} func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[77] + mi := &file_ateapi_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5589,7 +5953,7 @@ func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkerRequest.ProtoReflect.Descriptor instead. func (*GetWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{77} + return file_ateapi_proto_rawDescGZIP(), []int{83} } func (x *GetWorkerRequest) GetWorker() *ObjectRef { @@ -5611,7 +5975,7 @@ type CreateWorkerRequest struct { func (x *CreateWorkerRequest) Reset() { *x = CreateWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[78] + mi := &file_ateapi_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5623,7 +5987,7 @@ func (x *CreateWorkerRequest) String() string { func (*CreateWorkerRequest) ProtoMessage() {} func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[78] + mi := &file_ateapi_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5636,7 +6000,7 @@ func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkerRequest.ProtoReflect.Descriptor instead. func (*CreateWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{78} + return file_ateapi_proto_rawDescGZIP(), []int{84} } func (x *CreateWorkerRequest) GetWorker() *Worker { @@ -5671,7 +6035,7 @@ type UpdateWorkerRequest struct { func (x *UpdateWorkerRequest) Reset() { *x = UpdateWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[79] + mi := &file_ateapi_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5683,7 +6047,7 @@ func (x *UpdateWorkerRequest) String() string { func (*UpdateWorkerRequest) ProtoMessage() {} func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[79] + mi := &file_ateapi_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5696,7 +6060,7 @@ func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateWorkerRequest.ProtoReflect.Descriptor instead. func (*UpdateWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{79} + return file_ateapi_proto_rawDescGZIP(), []int{85} } func (x *UpdateWorkerRequest) GetWorker() *Worker { @@ -5723,7 +6087,7 @@ type DeleteWorkerRequest struct { func (x *DeleteWorkerRequest) Reset() { *x = DeleteWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[80] + mi := &file_ateapi_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5735,7 +6099,7 @@ func (x *DeleteWorkerRequest) String() string { func (*DeleteWorkerRequest) ProtoMessage() {} func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[80] + mi := &file_ateapi_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5748,7 +6112,7 @@ func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkerRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{80} + return file_ateapi_proto_rawDescGZIP(), []int{86} } func (x *DeleteWorkerRequest) GetWorker() *ObjectRef { @@ -5778,7 +6142,7 @@ type DrainWorkerRequest struct { func (x *DrainWorkerRequest) Reset() { *x = DrainWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[81] + mi := &file_ateapi_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5790,7 +6154,7 @@ func (x *DrainWorkerRequest) String() string { func (*DrainWorkerRequest) ProtoMessage() {} func (x *DrainWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[81] + mi := &file_ateapi_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5803,7 +6167,7 @@ func (x *DrainWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DrainWorkerRequest.ProtoReflect.Descriptor instead. func (*DrainWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{81} + return file_ateapi_proto_rawDescGZIP(), []int{87} } func (x *DrainWorkerRequest) GetWorker() *ObjectRef { @@ -5841,7 +6205,7 @@ type ListActorsRequest struct { func (x *ListActorsRequest) Reset() { *x = ListActorsRequest{} - mi := &file_ateapi_proto_msgTypes[82] + mi := &file_ateapi_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5853,7 +6217,7 @@ func (x *ListActorsRequest) String() string { func (*ListActorsRequest) ProtoMessage() {} func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[82] + mi := &file_ateapi_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5866,7 +6230,7 @@ func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsRequest.ProtoReflect.Descriptor instead. func (*ListActorsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{82} + return file_ateapi_proto_rawDescGZIP(), []int{88} } func (x *ListActorsRequest) GetAtespace() string { @@ -5902,7 +6266,7 @@ type ListActorsResponse struct { func (x *ListActorsResponse) Reset() { *x = ListActorsResponse{} - mi := &file_ateapi_proto_msgTypes[83] + mi := &file_ateapi_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5914,7 +6278,7 @@ func (x *ListActorsResponse) String() string { func (*ListActorsResponse) ProtoMessage() {} func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[83] + mi := &file_ateapi_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5927,7 +6291,7 @@ func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsResponse.ProtoReflect.Descriptor instead. func (*ListActorsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{83} + return file_ateapi_proto_rawDescGZIP(), []int{89} } func (x *ListActorsResponse) GetActors() []*Actor { @@ -6016,7 +6380,7 @@ type Worker struct { func (x *Worker) Reset() { *x = Worker{} - mi := &file_ateapi_proto_msgTypes[84] + mi := &file_ateapi_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6028,7 +6392,7 @@ func (x *Worker) String() string { func (*Worker) ProtoMessage() {} func (x *Worker) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[84] + mi := &file_ateapi_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6041,7 +6405,7 @@ func (x *Worker) ProtoReflect() protoreflect.Message { // Deprecated: Use Worker.ProtoReflect.Descriptor instead. func (*Worker) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{84} + return file_ateapi_proto_rawDescGZIP(), []int{90} } func (x *Worker) GetMetadata() *ResourceMetadata { @@ -6143,7 +6507,7 @@ type WorkerStatus struct { func (x *WorkerStatus) Reset() { *x = WorkerStatus{} - mi := &file_ateapi_proto_msgTypes[85] + mi := &file_ateapi_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6155,7 +6519,7 @@ func (x *WorkerStatus) String() string { func (*WorkerStatus) ProtoMessage() {} func (x *WorkerStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[85] + mi := &file_ateapi_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6168,7 +6532,7 @@ func (x *WorkerStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerStatus.ProtoReflect.Descriptor instead. func (*WorkerStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{85} + return file_ateapi_proto_rawDescGZIP(), []int{91} } func (x *WorkerStatus) GetState() WorkerState { @@ -6214,7 +6578,7 @@ type WorkerResources struct { func (x *WorkerResources) Reset() { *x = WorkerResources{} - mi := &file_ateapi_proto_msgTypes[86] + mi := &file_ateapi_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6226,7 +6590,7 @@ func (x *WorkerResources) String() string { func (*WorkerResources) ProtoMessage() {} func (x *WorkerResources) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[86] + mi := &file_ateapi_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6239,7 +6603,7 @@ func (x *WorkerResources) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerResources.ProtoReflect.Descriptor instead. func (*WorkerResources) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{86} + return file_ateapi_proto_rawDescGZIP(), []int{92} } func (x *WorkerResources) GetResources() *Resources { @@ -6293,7 +6657,7 @@ type ActorAssignment struct { func (x *ActorAssignment) Reset() { *x = ActorAssignment{} - mi := &file_ateapi_proto_msgTypes[87] + mi := &file_ateapi_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6305,7 +6669,7 @@ func (x *ActorAssignment) String() string { func (*ActorAssignment) ProtoMessage() {} func (x *ActorAssignment) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[87] + mi := &file_ateapi_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6318,7 +6682,7 @@ func (x *ActorAssignment) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorAssignment.ProtoReflect.Descriptor instead. func (*ActorAssignment) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{87} + return file_ateapi_proto_rawDescGZIP(), []int{93} } func (x *ActorAssignment) GetMetadata() *ResourceMetadata { @@ -6376,7 +6740,7 @@ type SetWorkerCapacityRequest struct { func (x *SetWorkerCapacityRequest) Reset() { *x = SetWorkerCapacityRequest{} - mi := &file_ateapi_proto_msgTypes[88] + mi := &file_ateapi_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6388,7 +6752,7 @@ func (x *SetWorkerCapacityRequest) String() string { func (*SetWorkerCapacityRequest) ProtoMessage() {} func (x *SetWorkerCapacityRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[88] + mi := &file_ateapi_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6401,7 +6765,7 @@ func (x *SetWorkerCapacityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWorkerCapacityRequest.ProtoReflect.Descriptor instead. func (*SetWorkerCapacityRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{88} + return file_ateapi_proto_rawDescGZIP(), []int{94} } func (x *SetWorkerCapacityRequest) GetWorker() *ObjectRef { @@ -6428,7 +6792,7 @@ type SetWorkerCapacityResponse struct { func (x *SetWorkerCapacityResponse) Reset() { *x = SetWorkerCapacityResponse{} - mi := &file_ateapi_proto_msgTypes[89] + mi := &file_ateapi_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6440,7 +6804,7 @@ func (x *SetWorkerCapacityResponse) String() string { func (*SetWorkerCapacityResponse) ProtoMessage() {} func (x *SetWorkerCapacityResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[89] + mi := &file_ateapi_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6453,7 +6817,7 @@ func (x *SetWorkerCapacityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWorkerCapacityResponse.ProtoReflect.Descriptor instead. func (*SetWorkerCapacityResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{89} + return file_ateapi_proto_rawDescGZIP(), []int{95} } func (x *SetWorkerCapacityResponse) GetWorker() *Worker { @@ -6463,290 +6827,6 @@ func (x *SetWorkerCapacityResponse) GetWorker() *Worker { return nil } -type MintJWTRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The audiences the minted JWT is bound to. Tokens are only issued with - // audience bindings, so at least one is required. - // - // +k8s:required - // +k8s:maxItems=16 # guardrail; tokens realistically bind a handful of audiences - // +k8s:listType=set - // +k8s:eachVal=+k8s:maxLength=512 # audiences are caller-defined URIs; bound only - Audience []string `protobuf:"bytes,1,rep,name=audience,proto3" json:"audience,omitempty"` - // +k8s:required - // +k8s:format=k8s-short-name - Atespace string `protobuf:"bytes,2,opt,name=atespace,proto3" json:"atespace,omitempty"` - // +k8s:required - // +k8s:format=k8s-short-name - ActorName string `protobuf:"bytes,3,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - // +k8s:optional - // +k8s:format=k8s-uuid - ActorUid string `protobuf:"bytes,4,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MintJWTRequest) Reset() { - *x = MintJWTRequest{} - mi := &file_ateapi_proto_msgTypes[90] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MintJWTRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MintJWTRequest) ProtoMessage() {} - -func (x *MintJWTRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[90] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MintJWTRequest.ProtoReflect.Descriptor instead. -func (*MintJWTRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{90} -} - -func (x *MintJWTRequest) GetAudience() []string { - if x != nil { - return x.Audience - } - return nil -} - -func (x *MintJWTRequest) GetAtespace() string { - if x != nil { - return x.Atespace - } - return "" -} - -func (x *MintJWTRequest) GetActorName() string { - if x != nil { - return x.ActorName - } - return "" -} - -func (x *MintJWTRequest) GetActorUid() string { - if x != nil { - return x.ActorUid - } - return "" -} - -// TODO: check why k8s do ":" and not "/" as a seprator for the Subject format -// TODO: whats the right format for the subject? kubernetes follow "system:serviceaccount::". -type MintJWTResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Actor JWT. An OIDC Discovery-compatible JWT - // - // Claims: - // - // * iss: Issuer - a valid URL where a relying party can fetch the OIDC - // discovery documents. - // * sub: Subject - a string expressing the identity carried in the - // credential. Format - // `atespaces:${atespace}:actors:${actorname}`. - // * aud: Audience - a string identifying the service this token will be used - // to authenticate to. - // * nbf: Not Before - a numeric unix timestamp - // * exp: Expiration - a numeric unix timestamp - // * iat: Issued At - a numeric unix timestamp - // * `ate.dev`: Ate/Substrate Extension - JSON object - // * atespace: (string) The atespace the actor belongs to - // * actorName: (string) The actor's name, unique within its atespace - ActorJwt string `protobuf:"bytes,1,opt,name=actor_jwt,json=actorJwt,proto3" json:"actor_jwt,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MintJWTResponse) Reset() { - *x = MintJWTResponse{} - mi := &file_ateapi_proto_msgTypes[91] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MintJWTResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MintJWTResponse) ProtoMessage() {} - -func (x *MintJWTResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[91] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MintJWTResponse.ProtoReflect.Descriptor instead. -func (*MintJWTResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{91} -} - -func (x *MintJWTResponse) GetActorJwt() string { - if x != nil { - return x.ActorJwt - } - return "" -} - -type MintCertRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The Worker the certificate is minted for, as authenticated by the - // node-local atelet. Workers are global-scoped, so this carries no atespace. - // Ateapi resolves the worker's current actor assignment rather than trusting - // actor metadata from the caller. - // - // This is the one caller that recovers a Worker name from a pod certificate: - // the atelet has only the worker Pod's identity to go on. Everywhere else the - // name is opaque and must be carried, not reconstructed. - // - // +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix - // +k8s:required - Worker *ObjectRef `protobuf:"bytes,1,opt,name=worker,proto3" json:"worker,omitempty"` - // Request contains DER encoded bytes of a x509 certificate signing request. - // The signer will ignore the contents of the CSR except to extract the - // subject public key. - // - // +k8s:required - // +k8s:customValidation # size bound; maxLength is string-only - CertificateSigningRequest []byte `protobuf:"bytes,2,opt,name=certificate_signing_request,json=certificateSigningRequest,proto3" json:"certificate_signing_request,omitempty"` - // Actor incarnation expected by the activation. This is only a stale-request - // guard: ateapi derives the actor and its identity from the worker assignment. - // - // +k8s:required - // +k8s:format=k8s-uuid - ExpectedActorUid string `protobuf:"bytes,3,opt,name=expected_actor_uid,json=expectedActorUid,proto3" json:"expected_actor_uid,omitempty"` - // +k8s:required - // +k8s:minimum=1 - // +k8s:maximum=1 # keep this in sync with the ActorCertificatePurpose enum - Purpose ActorCertificatePurpose `protobuf:"varint,4,opt,name=purpose,proto3,enum=ateapi.ActorCertificatePurpose" json:"purpose,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MintCertRequest) Reset() { - *x = MintCertRequest{} - mi := &file_ateapi_proto_msgTypes[92] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MintCertRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MintCertRequest) ProtoMessage() {} - -func (x *MintCertRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[92] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MintCertRequest.ProtoReflect.Descriptor instead. -func (*MintCertRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{92} -} - -func (x *MintCertRequest) GetWorker() *ObjectRef { - if x != nil { - return x.Worker - } - return nil -} - -func (x *MintCertRequest) GetCertificateSigningRequest() []byte { - if x != nil { - return x.CertificateSigningRequest - } - return nil -} - -func (x *MintCertRequest) GetExpectedActorUid() string { - if x != nil { - return x.ExpectedActorUid - } - return "" -} - -func (x *MintCertRequest) GetPurpose() ActorCertificatePurpose { - if x != nil { - return x.Purpose - } - return ActorCertificatePurpose_ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED -} - -type MintCertResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Response contains a list of DER encoded certificates. The first entry is the - // leaf certificate, and any remaining entries are intermediates in - // leaf-to-root order. - ActorCertificates [][]byte `protobuf:"bytes,1,rep,name=actor_certificates,json=actorCertificates,proto3" json:"actor_certificates,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MintCertResponse) Reset() { - *x = MintCertResponse{} - mi := &file_ateapi_proto_msgTypes[93] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MintCertResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MintCertResponse) ProtoMessage() {} - -func (x *MintCertResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[93] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MintCertResponse.ProtoReflect.Descriptor instead. -func (*MintCertResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{93} -} - -func (x *MintCertResponse) GetActorCertificates() [][]byte { - if x != nil { - return x.ActorCertificates - } - return nil -} - var File_ateapi_proto protoreflect.FileDescriptor const file_ateapi_proto_rawDesc = "" + @@ -6997,7 +7077,24 @@ const file_ateapi_proto_rawDesc = "" + "\x1eDeleteActorEgressPolicyRequest\x12'\n" + "\x05actor\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x05actor\"4\n" + "\rGetTagRequest\x12#\n" + - "\x03tag\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x03tag\"i\n" + + "\x03tag\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x03tag\"w\n" + + "\x13MintActorJWTRequest\x12'\n" + + "\x05actor\x18\x05 \x01(\v2\x11.ateapi.ObjectRefR\x05actor\x12\x1b\n" + + "\tactor_uid\x18\a \x01(\tR\bactorUid\x12\x1a\n" + + "\baudience\x18\x01 \x03(\tR\baudience\"3\n" + + "\x14MintActorJWTResponse\x12\x1b\n" + + "\tactor_jwt\x18\x01 \x01(\tR\bactorJwt\"\xde\x01\n" + + "\x1bMintActorCertificateRequest\x12'\n" + + "\x05actor\x18\x06 \x01(\v2\x11.ateapi.ObjectRefR\x05actor\x12\x1b\n" + + "\tactor_uid\x18\a \x01(\tR\bactorUid\x12>\n" + + "\x1bcertificate_signing_request\x18\x02 \x01(\fR\x19certificateSigningRequest\x129\n" + + "\apurpose\x18\x04 \x01(\x0e2\x1f.ateapi.ActorCertificatePurposeR\apurpose\"M\n" + + "\x1cMintActorCertificateResponse\x12-\n" + + "\x12actor_certificates\x18\x01 \x03(\fR\x11actorCertificates\"S\n" + + "\x17GetActorSnapshotRequest\x128\n" + + "\x0eactor_snapshot\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\ractorSnapshot\"]\n" + + "\x1aGetActorSnapshotTagRequest\x12?\n" + + "\x12actor_snapshot_tag\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x10actorSnapshotTag\"i\n" + "\x0fListTagsRequest\x12\x1a\n" + "\batespace\x18\x01 \x01(\tR\batespace\x12\x1b\n" + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" + @@ -7082,22 +7179,7 @@ const file_ateapi_proto_rawDesc = "" + "\x06worker\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x06worker\x123\n" + "\bcapacity\x18\x02 \x01(\v2\x17.ateapi.WorkerResourcesR\bcapacity\"C\n" + "\x19SetWorkerCapacityResponse\x12&\n" + - "\x06worker\x18\x01 \x01(\v2\x0e.ateapi.WorkerR\x06worker\"\x84\x01\n" + - "\x0eMintJWTRequest\x12\x1a\n" + - "\baudience\x18\x01 \x03(\tR\baudience\x12\x1a\n" + - "\batespace\x18\x02 \x01(\tR\batespace\x12\x1d\n" + - "\n" + - "actor_name\x18\x03 \x01(\tR\tactorName\x12\x1b\n" + - "\tactor_uid\x18\x04 \x01(\tR\bactorUid\".\n" + - "\x0fMintJWTResponse\x12\x1b\n" + - "\tactor_jwt\x18\x01 \x01(\tR\bactorJwt\"\xe5\x01\n" + - "\x0fMintCertRequest\x12)\n" + - "\x06worker\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x06worker\x12>\n" + - "\x1bcertificate_signing_request\x18\x02 \x01(\fR\x19certificateSigningRequest\x12,\n" + - "\x12expected_actor_uid\x18\x03 \x01(\tR\x10expectedActorUid\x129\n" + - "\apurpose\x18\x04 \x01(\x0e2\x1f.ateapi.ActorCertificatePurposeR\apurpose\"A\n" + - "\x10MintCertResponse\x12-\n" + - "\x12actor_certificates\x18\x01 \x03(\fR\x11actorCertificates*\x80\x01\n" + + "\x06worker\x18\x01 \x01(\v2\x0e.ateapi.WorkerR\x06worker*\x80\x01\n" + "\x14SnapshotContentScope\x12&\n" + "\"SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED\x10\x00\x12\x1f\n" + "\x1bSNAPSHOT_CONTENT_SCOPE_FULL\x10\x01\x12\x1f\n" + @@ -7129,14 +7211,14 @@ const file_ateapi_proto_rawDesc = "" + " ACTOR_METADATA_FIELD_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19ACTOR_METADATA_FIELD_NAME\x10\x01\x12!\n" + "\x1dACTOR_METADATA_FIELD_ATESPACE\x10\x02\x12\x1c\n" + - "\x18ACTOR_METADATA_FIELD_UID\x10\x03*_\n" + + "\x18ACTOR_METADATA_FIELD_UID\x10\x03*k\n" + + "\x17ActorCertificatePurpose\x12)\n" + + "%ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED\x10\x00\x12%\n" + + "!ACTOR_CERTIFICATE_PURPOSE_ATUNNEL\x10\x01*_\n" + "\vWorkerState\x12\x1c\n" + "\x18WORKER_STATE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13WORKER_STATE_ACTIVE\x10\x01\x12\x19\n" + - "\x15WORKER_STATE_DRAINING\x10\x02*k\n" + - "\x17ActorCertificatePurpose\x12)\n" + - "%ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED\x10\x00\x12%\n" + - "!ACTOR_CERTIFICATE_PURPOSE_ATUNNEL\x10\x012\xf3\x11\n" + + "\x15WORKER_STATE_DRAINING\x10\x022\xa5\x13\n" + "\aControl\x124\n" + "\bGetActor\x12\x17.ateapi.GetActorRequest\x1a\r.ateapi.Actor\"\x00\x12:\n" + "\vCreateActor\x12\x1a.ateapi.CreateActorRequest\x1a\r.ateapi.Actor\"\x00\x12:\n" + @@ -7149,7 +7231,9 @@ const file_ateapi_proto_rawDesc = "" + "\x14GetActorEgressPolicy\x12#.ateapi.GetActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n" + "\x17CreateActorEgressPolicy\x12&.ateapi.CreateActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n" + "\x17UpdateActorEgressPolicy\x12&.ateapi.UpdateActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n" + - "\x17DeleteActorEgressPolicy\x12&.ateapi.DeleteActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x124\n" + + "\x17DeleteActorEgressPolicy\x12&.ateapi.DeleteActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12K\n" + + "\fMintActorJWT\x12\x1b.ateapi.MintActorJWTRequest\x1a\x1c.ateapi.MintActorJWTResponse\"\x00\x12c\n" + + "\x14MintActorCertificate\x12#.ateapi.MintActorCertificateRequest\x1a$.ateapi.MintActorCertificateResponse\"\x00\x124\n" + "\tCreateTag\x12\x18.ateapi.CreateTagRequest\x1a\v.ateapi.Tag\"\x00\x12.\n" + "\x06GetTag\x12\x15.ateapi.GetTagRequest\x1a\v.ateapi.Tag\"\x00\x12?\n" + "\bListTags\x12\x17.ateapi.ListTagsRequest\x1a\x18.ateapi.ListTagsResponse\"\x00\x124\n" + @@ -7171,10 +7255,7 @@ const file_ateapi_proto_rawDesc = "" + "\x13CreateActorTemplate\x12\".ateapi.CreateActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x00\x12L\n" + "\x10GetActorTemplate\x12\x1f.ateapi.GetActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x00\x12]\n" + "\x12ListActorTemplates\x12!.ateapi.ListActorTemplatesRequest\x1a\".ateapi.ListActorTemplatesResponse\"\x00\x12R\n" + - "\x13DeleteActorTemplate\x12\".ateapi.DeleteActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x002\x8a\x01\n" + - "\rActorIdentity\x12:\n" + - "\aMintJWT\x12\x16.ateapi.MintJWTRequest\x1a\x17.ateapi.MintJWTResponse\x12=\n" + - "\bMintCert\x12\x17.ateapi.MintCertRequest\x1a\x18.ateapi.MintCertResponse2i\n" + + "\x13DeleteActorTemplate\x12\".ateapi.DeleteActorTemplateRequest\x1a\x15.ateapi.ActorTemplate\"\x002i\n" + "\rWorkerService\x12X\n" + "\x11SetWorkerCapacity\x12 .ateapi.SetWorkerCapacityRequest\x1a!.ateapi.SetWorkerCapacityResponseB9Z7github.com/agent-substrate/substrate/pkg/proto/ateapipbb\x06proto3" @@ -7191,7 +7272,7 @@ func file_ateapi_proto_rawDescGZIP() []byte { } var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 9) -var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 97) +var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 99) var file_ateapi_proto_goTypes = []any{ (SnapshotContentScope)(0), // 0: ateapi.SnapshotContentScope (TagScope)(0), // 1: ateapi.TagScope @@ -7199,8 +7280,8 @@ var file_ateapi_proto_goTypes = []any{ (SandboxClass)(0), // 3: ateapi.SandboxClass (ResumeSource)(0), // 4: ateapi.ResumeSource (ActorMetadataField)(0), // 5: ateapi.ActorMetadataField - (WorkerState)(0), // 6: ateapi.WorkerState - (ActorCertificatePurpose)(0), // 7: ateapi.ActorCertificatePurpose + (ActorCertificatePurpose)(0), // 6: ateapi.ActorCertificatePurpose + (WorkerState)(0), // 7: ateapi.WorkerState (ExternalVolume_Status)(0), // 8: ateapi.ExternalVolume.Status (*ExternalSnapshot)(nil), // 9: ateapi.ExternalSnapshot (*LocalSnapshotInfo)(nil), // 10: ateapi.LocalSnapshotInfo @@ -7269,47 +7350,49 @@ var file_ateapi_proto_goTypes = []any{ (*UpdateActorEgressPolicyRequest)(nil), // 73: ateapi.UpdateActorEgressPolicyRequest (*DeleteActorEgressPolicyRequest)(nil), // 74: ateapi.DeleteActorEgressPolicyRequest (*GetTagRequest)(nil), // 75: ateapi.GetTagRequest - (*ListTagsRequest)(nil), // 76: ateapi.ListTagsRequest - (*ListTagsResponse)(nil), // 77: ateapi.ListTagsResponse - (*CreateTagRequest)(nil), // 78: ateapi.CreateTagRequest - (*UpdateTagRequest)(nil), // 79: ateapi.UpdateTagRequest - (*DeleteTagRequest)(nil), // 80: ateapi.DeleteTagRequest - (*DeleteOptions)(nil), // 81: ateapi.DeleteOptions - (*ListWorkerActorAssignmentsRequest)(nil), // 82: ateapi.ListWorkerActorAssignmentsRequest - (*ListWorkerActorAssignmentsResponse)(nil), // 83: ateapi.ListWorkerActorAssignmentsResponse - (*ListWorkersRequest)(nil), // 84: ateapi.ListWorkersRequest - (*ListWorkersResponse)(nil), // 85: ateapi.ListWorkersResponse - (*GetWorkerRequest)(nil), // 86: ateapi.GetWorkerRequest - (*CreateWorkerRequest)(nil), // 87: ateapi.CreateWorkerRequest - (*UpdateWorkerRequest)(nil), // 88: ateapi.UpdateWorkerRequest - (*DeleteWorkerRequest)(nil), // 89: ateapi.DeleteWorkerRequest - (*DrainWorkerRequest)(nil), // 90: ateapi.DrainWorkerRequest - (*ListActorsRequest)(nil), // 91: ateapi.ListActorsRequest - (*ListActorsResponse)(nil), // 92: ateapi.ListActorsResponse - (*Worker)(nil), // 93: ateapi.Worker - (*WorkerStatus)(nil), // 94: ateapi.WorkerStatus - (*WorkerResources)(nil), // 95: ateapi.WorkerResources - (*ActorAssignment)(nil), // 96: ateapi.ActorAssignment - (*SetWorkerCapacityRequest)(nil), // 97: ateapi.SetWorkerCapacityRequest - (*SetWorkerCapacityResponse)(nil), // 98: ateapi.SetWorkerCapacityResponse - (*MintJWTRequest)(nil), // 99: ateapi.MintJWTRequest - (*MintJWTResponse)(nil), // 100: ateapi.MintJWTResponse - (*MintCertRequest)(nil), // 101: ateapi.MintCertRequest - (*MintCertResponse)(nil), // 102: ateapi.MintCertResponse - nil, // 103: ateapi.Selector.MatchLabelsEntry - nil, // 104: ateapi.ExternalVolume.VolumeContextEntry - nil, // 105: ateapi.Worker.LabelsEntry - (*timestamppb.Timestamp)(nil), // 106: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 107: google.protobuf.Empty + (*MintActorJWTRequest)(nil), // 76: ateapi.MintActorJWTRequest + (*MintActorJWTResponse)(nil), // 77: ateapi.MintActorJWTResponse + (*MintActorCertificateRequest)(nil), // 78: ateapi.MintActorCertificateRequest + (*MintActorCertificateResponse)(nil), // 79: ateapi.MintActorCertificateResponse + (*GetActorSnapshotRequest)(nil), // 80: ateapi.GetActorSnapshotRequest + (*GetActorSnapshotTagRequest)(nil), // 81: ateapi.GetActorSnapshotTagRequest + (*ListTagsRequest)(nil), // 82: ateapi.ListTagsRequest + (*ListTagsResponse)(nil), // 83: ateapi.ListTagsResponse + (*CreateTagRequest)(nil), // 84: ateapi.CreateTagRequest + (*UpdateTagRequest)(nil), // 85: ateapi.UpdateTagRequest + (*DeleteTagRequest)(nil), // 86: ateapi.DeleteTagRequest + (*DeleteOptions)(nil), // 87: ateapi.DeleteOptions + (*ListWorkerActorAssignmentsRequest)(nil), // 88: ateapi.ListWorkerActorAssignmentsRequest + (*ListWorkerActorAssignmentsResponse)(nil), // 89: ateapi.ListWorkerActorAssignmentsResponse + (*ListWorkersRequest)(nil), // 90: ateapi.ListWorkersRequest + (*ListWorkersResponse)(nil), // 91: ateapi.ListWorkersResponse + (*GetWorkerRequest)(nil), // 92: ateapi.GetWorkerRequest + (*CreateWorkerRequest)(nil), // 93: ateapi.CreateWorkerRequest + (*UpdateWorkerRequest)(nil), // 94: ateapi.UpdateWorkerRequest + (*DeleteWorkerRequest)(nil), // 95: ateapi.DeleteWorkerRequest + (*DrainWorkerRequest)(nil), // 96: ateapi.DrainWorkerRequest + (*ListActorsRequest)(nil), // 97: ateapi.ListActorsRequest + (*ListActorsResponse)(nil), // 98: ateapi.ListActorsResponse + (*Worker)(nil), // 99: ateapi.Worker + (*WorkerStatus)(nil), // 100: ateapi.WorkerStatus + (*WorkerResources)(nil), // 101: ateapi.WorkerResources + (*ActorAssignment)(nil), // 102: ateapi.ActorAssignment + (*SetWorkerCapacityRequest)(nil), // 103: ateapi.SetWorkerCapacityRequest + (*SetWorkerCapacityResponse)(nil), // 104: ateapi.SetWorkerCapacityResponse + nil, // 105: ateapi.Selector.MatchLabelsEntry + nil, // 106: ateapi.ExternalVolume.VolumeContextEntry + nil, // 107: ateapi.Worker.LabelsEntry + (*timestamppb.Timestamp)(nil), // 108: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 109: google.protobuf.Empty } var file_ateapi_proto_depIdxs = []int32{ 0, // 0: ateapi.ExternalSnapshot.content_scope:type_name -> ateapi.SnapshotContentScope 0, // 1: ateapi.LocalSnapshotInfo.content_scope:type_name -> ateapi.SnapshotContentScope - 103, // 2: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry - 106, // 3: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp - 106, // 4: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp + 105, // 2: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry + 108, // 3: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp + 108, // 4: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp 8, // 5: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status - 104, // 6: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry + 106, // 6: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry 12, // 7: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata 26, // 8: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef 11, // 9: ateapi.Actor.worker_selector:type_name -> ateapi.Selector @@ -7319,7 +7402,7 @@ var file_ateapi_proto_depIdxs = []int32{ 16, // 13: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule 17, // 14: ateapi.EgressRule.hostnames:type_name -> ateapi.HostnameRule 18, // 15: ateapi.EgressRule.ip_blocks:type_name -> ateapi.IPBlockRule - 107, // 16: ateapi.EgressRule.all:type_name -> google.protobuf.Empty + 109, // 16: ateapi.EgressRule.all:type_name -> google.protobuf.Empty 19, // 17: ateapi.HostnameRule.effects:type_name -> ateapi.EgressRuleEffects 20, // 18: ateapi.EgressRuleEffects.inject_static_headers:type_name -> ateapi.CredentialHeaderInjection 2, // 19: ateapi.ActorStatus.state:type_name -> ateapi.ActorState @@ -7344,7 +7427,7 @@ var file_ateapi_proto_depIdxs = []int32{ 31, // 38: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus 29, // 39: ateapi.Resources.limits:type_name -> ateapi.Limits 9, // 40: ateapi.GoldenSnapshotStatus.golden_snapshot:type_name -> ateapi.ExternalSnapshot - 106, // 41: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp + 108, // 41: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp 30, // 42: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus 3, // 43: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass 0, // 44: ateapi.SnapshotsConfig.on_pause:type_name -> ateapi.SnapshotContentScope @@ -7392,111 +7475,114 @@ var file_ateapi_proto_depIdxs = []int32{ 15, // 86: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy 26, // 87: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef 26, // 88: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef - 24, // 89: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag - 24, // 90: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag - 24, // 91: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag - 26, // 92: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef - 26, // 93: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef - 96, // 94: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment - 93, // 95: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker - 26, // 96: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef - 93, // 97: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker - 93, // 98: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker - 26, // 99: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef - 81, // 100: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions - 26, // 101: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef - 14, // 102: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor - 12, // 103: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata - 105, // 104: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry - 94, // 105: ateapi.Worker.status:type_name -> ateapi.WorkerStatus - 6, // 106: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState - 95, // 107: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources - 95, // 108: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources - 28, // 109: ateapi.WorkerResources.resources:type_name -> ateapi.Resources - 12, // 110: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata - 26, // 111: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef - 26, // 112: ateapi.ActorAssignment.actor_template_ref:type_name -> ateapi.ObjectRef - 28, // 113: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources - 26, // 114: ateapi.SetWorkerCapacityRequest.worker:type_name -> ateapi.ObjectRef - 95, // 115: ateapi.SetWorkerCapacityRequest.capacity:type_name -> ateapi.WorkerResources - 93, // 116: ateapi.SetWorkerCapacityResponse.worker:type_name -> ateapi.Worker - 26, // 117: ateapi.MintCertRequest.worker:type_name -> ateapi.ObjectRef - 7, // 118: ateapi.MintCertRequest.purpose:type_name -> ateapi.ActorCertificatePurpose - 61, // 119: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest - 62, // 120: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest - 63, // 121: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest - 64, // 122: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest - 66, // 123: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest - 68, // 124: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest - 70, // 125: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest - 71, // 126: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest - 72, // 127: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest - 73, // 128: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest - 74, // 129: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest - 78, // 130: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest - 75, // 131: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest - 76, // 132: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest - 79, // 133: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest - 80, // 134: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest - 84, // 135: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest - 86, // 136: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest - 87, // 137: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest - 88, // 138: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest - 89, // 139: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest - 90, // 140: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest - 82, // 141: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest - 91, // 142: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest - 51, // 143: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest - 52, // 144: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest - 53, // 145: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest - 55, // 146: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest - 56, // 147: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest - 57, // 148: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest - 58, // 149: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest - 60, // 150: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest - 99, // 151: ateapi.ActorIdentity.MintJWT:input_type -> ateapi.MintJWTRequest - 101, // 152: ateapi.ActorIdentity.MintCert:input_type -> ateapi.MintCertRequest - 97, // 153: ateapi.WorkerService.SetWorkerCapacity:input_type -> ateapi.SetWorkerCapacityRequest - 14, // 154: ateapi.Control.GetActor:output_type -> ateapi.Actor - 14, // 155: ateapi.Control.CreateActor:output_type -> ateapi.Actor - 14, // 156: ateapi.Control.UpdateActor:output_type -> ateapi.Actor - 65, // 157: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse - 67, // 158: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse - 69, // 159: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse - 14, // 160: ateapi.Control.DeleteActor:output_type -> ateapi.Actor - 15, // 161: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy - 15, // 162: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 15, // 163: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 15, // 164: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy - 24, // 165: ateapi.Control.CreateTag:output_type -> ateapi.Tag - 24, // 166: ateapi.Control.GetTag:output_type -> ateapi.Tag - 77, // 167: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse - 24, // 168: ateapi.Control.UpdateTag:output_type -> ateapi.Tag - 24, // 169: ateapi.Control.DeleteTag:output_type -> ateapi.Tag - 85, // 170: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse - 93, // 171: ateapi.Control.GetWorker:output_type -> ateapi.Worker - 93, // 172: ateapi.Control.CreateWorker:output_type -> ateapi.Worker - 93, // 173: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker - 93, // 174: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker - 93, // 175: ateapi.Control.DrainWorker:output_type -> ateapi.Worker - 83, // 176: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse - 92, // 177: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse - 25, // 178: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace - 25, // 179: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace - 54, // 180: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse - 25, // 181: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace - 27, // 182: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate - 27, // 183: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate - 59, // 184: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse - 27, // 185: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate - 100, // 186: ateapi.ActorIdentity.MintJWT:output_type -> ateapi.MintJWTResponse - 102, // 187: ateapi.ActorIdentity.MintCert:output_type -> ateapi.MintCertResponse - 98, // 188: ateapi.WorkerService.SetWorkerCapacity:output_type -> ateapi.SetWorkerCapacityResponse - 154, // [154:189] is the sub-list for method output_type - 119, // [119:154] is the sub-list for method input_type - 119, // [119:119] is the sub-list for extension type_name - 119, // [119:119] is the sub-list for extension extendee - 0, // [0:119] is the sub-list for field type_name + 26, // 89: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef + 26, // 90: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef + 6, // 91: ateapi.MintActorCertificateRequest.purpose:type_name -> ateapi.ActorCertificatePurpose + 26, // 92: ateapi.GetActorSnapshotRequest.actor_snapshot:type_name -> ateapi.ObjectRef + 26, // 93: ateapi.GetActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ObjectRef + 24, // 94: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag + 24, // 95: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag + 24, // 96: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag + 26, // 97: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef + 26, // 98: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef + 102, // 99: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment + 99, // 100: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker + 26, // 101: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef + 99, // 102: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker + 99, // 103: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker + 26, // 104: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef + 87, // 105: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions + 26, // 106: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef + 14, // 107: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor + 12, // 108: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata + 107, // 109: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry + 100, // 110: ateapi.Worker.status:type_name -> ateapi.WorkerStatus + 7, // 111: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState + 101, // 112: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources + 101, // 113: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources + 28, // 114: ateapi.WorkerResources.resources:type_name -> ateapi.Resources + 12, // 115: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata + 26, // 116: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef + 26, // 117: ateapi.ActorAssignment.actor_template_ref:type_name -> ateapi.ObjectRef + 28, // 118: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources + 26, // 119: ateapi.SetWorkerCapacityRequest.worker:type_name -> ateapi.ObjectRef + 101, // 120: ateapi.SetWorkerCapacityRequest.capacity:type_name -> ateapi.WorkerResources + 99, // 121: ateapi.SetWorkerCapacityResponse.worker:type_name -> ateapi.Worker + 61, // 122: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest + 62, // 123: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest + 63, // 124: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest + 64, // 125: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest + 66, // 126: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest + 68, // 127: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest + 70, // 128: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest + 71, // 129: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest + 72, // 130: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest + 73, // 131: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest + 74, // 132: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest + 76, // 133: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest + 78, // 134: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest + 84, // 135: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest + 75, // 136: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest + 82, // 137: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest + 85, // 138: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest + 86, // 139: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest + 90, // 140: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest + 92, // 141: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest + 93, // 142: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest + 94, // 143: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest + 95, // 144: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest + 96, // 145: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest + 88, // 146: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest + 97, // 147: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest + 51, // 148: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest + 52, // 149: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest + 53, // 150: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest + 55, // 151: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest + 56, // 152: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest + 57, // 153: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest + 58, // 154: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest + 60, // 155: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest + 103, // 156: ateapi.WorkerService.SetWorkerCapacity:input_type -> ateapi.SetWorkerCapacityRequest + 14, // 157: ateapi.Control.GetActor:output_type -> ateapi.Actor + 14, // 158: ateapi.Control.CreateActor:output_type -> ateapi.Actor + 14, // 159: ateapi.Control.UpdateActor:output_type -> ateapi.Actor + 65, // 160: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse + 67, // 161: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse + 69, // 162: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse + 14, // 163: ateapi.Control.DeleteActor:output_type -> ateapi.Actor + 15, // 164: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy + 15, // 165: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 15, // 166: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 15, // 167: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy + 77, // 168: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse + 79, // 169: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse + 24, // 170: ateapi.Control.CreateTag:output_type -> ateapi.Tag + 24, // 171: ateapi.Control.GetTag:output_type -> ateapi.Tag + 83, // 172: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse + 24, // 173: ateapi.Control.UpdateTag:output_type -> ateapi.Tag + 24, // 174: ateapi.Control.DeleteTag:output_type -> ateapi.Tag + 91, // 175: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse + 99, // 176: ateapi.Control.GetWorker:output_type -> ateapi.Worker + 99, // 177: ateapi.Control.CreateWorker:output_type -> ateapi.Worker + 99, // 178: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker + 99, // 179: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker + 99, // 180: ateapi.Control.DrainWorker:output_type -> ateapi.Worker + 89, // 181: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse + 98, // 182: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse + 25, // 183: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace + 25, // 184: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace + 54, // 185: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse + 25, // 186: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace + 27, // 187: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate + 27, // 188: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate + 59, // 189: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse + 27, // 190: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate + 104, // 191: ateapi.WorkerService.SetWorkerCapacity:output_type -> ateapi.SetWorkerCapacityResponse + 157, // [157:192] is the sub-list for method output_type + 122, // [122:157] is the sub-list for method input_type + 122, // [122:122] is the sub-list for extension type_name + 122, // [122:122] is the sub-list for extension extendee + 0, // [0:122] is the sub-list for field type_name } func init() { file_ateapi_proto_init() } @@ -7510,9 +7596,9 @@ func file_ateapi_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_ateapi_proto_rawDesc), len(file_ateapi_proto_rawDesc)), NumEnums: 9, - NumMessages: 97, + NumMessages: 99, NumExtensions: 0, - NumServices: 3, + NumServices: 2, }, GoTypes: file_ateapi_proto_goTypes, DependencyIndexes: file_ateapi_proto_depIdxs, diff --git a/pkg/proto/ateapipb/ateapi.proto b/pkg/proto/ateapipb/ateapi.proto index a581f77796..7016446ea6 100644 --- a/pkg/proto/ateapipb/ateapi.proto +++ b/pkg/proto/ateapipb/ateapi.proto @@ -57,6 +57,19 @@ service Control { // Delete the egress policy resource nested under an Actor. rpc DeleteActorEgressPolicy(DeleteActorEgressPolicyRequest) returns (EgressPolicy) {} + // Create a Substrate-issued JWT asserting the actor identity. + // + // * Called by the egress gateway when actor JWT injection is configured for outbound requests. + rpc MintActorJWT(MintActorJWTRequest) returns (MintActorJWTResponse) {} + + // Create a Substrate-issued SPIFFE certificate asserting the actor identity. + // + // * Called by atelet to provision an atunnel with a certificate for + // communication with the egress gateway. TODO(ahmedtd): Migrate this use + // case to a distinct certificate to prevent actor/atunnel confusion. + // * Called by the egress gateway when actor client certificate injection is + // configured for outbound requests. + rpc MintActorCertificate(MintActorCertificateRequest) returns (MintActorCertificateResponse) {} // Tag the external snapshot a suspended Actor holds. The tag gets its own // copy of that snapshot, so suspending or deleting the Actor afterwards @@ -1438,6 +1451,106 @@ message GetTagRequest { ObjectRef tag = 1; } +message MintActorJWTRequest { + // The actor for which the JWT should be issued. + // + // Must be a valid actor that currently exists according to the actor store. + // + // +k8s:required + ObjectRef actor = 5; + + // The UID of the actor --- used to guard against deletion and recreation of + // an actor with the same name. + // + // +k8s:required + // +k8s:format=k8s-uuid + string actor_uid = 7; + + // The audiences the minted JWT is bound to. Tokens are only issued with + // audience bindings, so at least one is required. + // + // +k8s:required + // +k8s:maxItems=16 # guardrail; tokens realistically bind a handful of audiences + // +k8s:listType=set + // +k8s:eachVal=+k8s:maxLength=512 # audiences are caller-defined URIs; bound only + repeated string audience = 1; +} + +// TODO: check why k8s do ":" and not "/" as a seprator for the Subject format +// TODO: whats the right format for the subject? kubernetes follow "system:serviceaccount::". +message MintActorJWTResponse { + // Actor JWT. An OIDC Discovery-compatible JWT + // + // Claims: + // + // * iss: Issuer - a valid URL where a relying party can fetch the OIDC + // discovery documents. + // * sub: Subject - a string expressing the identity carried in the + // credential. Format + // `atespaces:${atespace}:actors:${actorname}`. + // * aud: Audience - a string identifying the service this token will be used + // to authenticate to. + // * nbf: Not Before - a numeric unix timestamp + // * exp: Expiration - a numeric unix timestamp + // * iat: Issued At - a numeric unix timestamp + // * `ate.dev`: Ate/Substrate Extension - JSON object + // * atespace: (string) The atespace the actor belongs to + // * actorName: (string) The actor's name, unique within its atespace + string actor_jwt = 1; +} + +message MintActorCertificateRequest { + // The actor for which the certificate should be issued. + // + // Must be a valid actor that currently exists according to the actor store. + // + // +k8s:required + ObjectRef actor = 6; + + // The UID of the actor --- used to guard against deletion and recreation of + // an actor with the same name. + // + // +k8s:required + // +k8s:format=k8s-uuid + string actor_uid = 7; + + // Request contains DER encoded bytes of a x509 certificate signing request. + // The signer will ignore the contents of the CSR except to extract the + // subject public key. + // + // +k8s:required + // +k8s:customValidation # size bound; maxLength is string-only + bytes certificate_signing_request = 2; + + // +k8s:required + // +k8s:minimum=1 + // +k8s:maximum=1 # keep this in sync with the ActorCertificatePurpose enum + ActorCertificatePurpose purpose = 4; +} + +enum ActorCertificatePurpose { + ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED = 0; + ACTOR_CERTIFICATE_PURPOSE_ATUNNEL = 1; + // Keep this in sync with MintCertRequest.purpose's maximum. +} + +message MintActorCertificateResponse { + // Response contains a list of DER encoded certificates. The first entry is the + // leaf certificate, and any remaining entries are intermediates in + // leaf-to-root order. + repeated bytes actor_certificates = 1; +} + +message GetActorSnapshotRequest { + // +k8s:opaqueType + ObjectRef actor_snapshot = 1; +} + +message GetActorSnapshotTagRequest { + // +k8s:opaqueType + ObjectRef actor_snapshot_tag = 1; +} + message ListTagsRequest { // The atespace to list tags from. Empty lists across all atespaces. // @@ -1843,33 +1956,6 @@ message ActorAssignment { Resources resources = 5; } -// ActorIdentity allows substrate workloads to exchange their -// infrastructure-level credentials (k8s service account token, etc.) for a -// substrate actor-level credential. A given substrate actor might migrate -// between many different physical workers over the course of its lifecycle, -// whereas the actor credential's identity will be stable for the life of the -// actor. -service ActorIdentity { - // Request an Actor Identity JWT. - // - // To call this RPC, you must be authenticated as the Kubernetes Pod that is - // currently running the requested actor. - rpc MintJWT(MintJWTRequest) returns (MintJWTResponse); - - // Request an Actor Identity Certificate for an actor. - // - // Actors do not call this RPC themselves. The atelet hosting the actor calls - // it on the actor's behalf, authenticating with its own client certificate - // rather than a bearer token. - // - // Authorization is decided on that client certificate and the worker - // identity attested by atelet. Ateapi verifies that the worker is assigned to - // the actor and that the actor points back to that exact worker before signing. - // - // The certificate in the response is the actor's identity, not the atelet's. - rpc MintCert(MintCertRequest) returns (MintCertResponse); -} - // WorkerService is how a Worker tells the control plane about itself. It is // separate from Control because the two have different callers and different // authorization: Control is the client-facing API, while these RPCs are served @@ -1905,97 +1991,3 @@ message SetWorkerCapacityResponse { // The Worker as recorded, so a caller sees what its report resolved to. Worker worker = 1; } - -message MintJWTRequest { - // The audiences the minted JWT is bound to. Tokens are only issued with - // audience bindings, so at least one is required. - // - // +k8s:required - // +k8s:maxItems=16 # guardrail; tokens realistically bind a handful of audiences - // +k8s:listType=set - // +k8s:eachVal=+k8s:maxLength=512 # audiences are caller-defined URIs; bound only - repeated string audience = 1; - - // +k8s:required - // +k8s:format=k8s-short-name - string atespace = 2; - - // +k8s:required - // +k8s:format=k8s-short-name - string actor_name = 3; - - // +k8s:optional - // +k8s:format=k8s-uuid - string actor_uid = 4; -} - -// TODO: check why k8s do ":" and not "/" as a seprator for the Subject format -// TODO: whats the right format for the subject? kubernetes follow "system:serviceaccount::". -message MintJWTResponse { - // Actor JWT. An OIDC Discovery-compatible JWT - // - // Claims: - // - // * iss: Issuer - a valid URL where a relying party can fetch the OIDC - // discovery documents. - // * sub: Subject - a string expressing the identity carried in the - // credential. Format - // `atespaces:${atespace}:actors:${actorname}`. - // * aud: Audience - a string identifying the service this token will be used - // to authenticate to. - // * nbf: Not Before - a numeric unix timestamp - // * exp: Expiration - a numeric unix timestamp - // * iat: Issued At - a numeric unix timestamp - // * `ate.dev`: Ate/Substrate Extension - JSON object - // * atespace: (string) The atespace the actor belongs to - // * actorName: (string) The actor's name, unique within its atespace - string actor_jwt = 1; -} - -message MintCertRequest { - // The Worker the certificate is minted for, as authenticated by the - // node-local atelet. Workers are global-scoped, so this carries no atespace. - // Ateapi resolves the worker's current actor assignment rather than trusting - // actor metadata from the caller. - // - // This is the one caller that recovers a Worker name from a pod certificate: - // the atelet has only the worker Pod's identity to go on. Everywhere else the - // name is opaque and must be carried, not reconstructed. - // - // +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix - // +k8s:required - ObjectRef worker = 1; - - // Request contains DER encoded bytes of a x509 certificate signing request. - // The signer will ignore the contents of the CSR except to extract the - // subject public key. - // - // +k8s:required - // +k8s:customValidation # size bound; maxLength is string-only - bytes certificate_signing_request = 2; - - // Actor incarnation expected by the activation. This is only a stale-request - // guard: ateapi derives the actor and its identity from the worker assignment. - // - // +k8s:required - // +k8s:format=k8s-uuid - string expected_actor_uid = 3; - - // +k8s:required - // +k8s:minimum=1 - // +k8s:maximum=1 # keep this in sync with the ActorCertificatePurpose enum - ActorCertificatePurpose purpose = 4; -} - -enum ActorCertificatePurpose { - ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED = 0; - ACTOR_CERTIFICATE_PURPOSE_ATUNNEL = 1; - // Keep this in sync with MintCertRequest.purpose's maximum. -} - -message MintCertResponse { - // Response contains a list of DER encoded certificates. The first entry is the - // leaf certificate, and any remaining entries are intermediates in - // leaf-to-root order. - repeated bytes actor_certificates = 1; -} diff --git a/pkg/proto/ateapipb/ateapi_grpc.pb.go b/pkg/proto/ateapipb/ateapi_grpc.pb.go index fcfbe31cc0..4910198c98 100644 --- a/pkg/proto/ateapipb/ateapi_grpc.pb.go +++ b/pkg/proto/ateapipb/ateapi_grpc.pb.go @@ -44,6 +44,8 @@ const ( Control_CreateActorEgressPolicy_FullMethodName = "/ateapi.Control/CreateActorEgressPolicy" Control_UpdateActorEgressPolicy_FullMethodName = "/ateapi.Control/UpdateActorEgressPolicy" Control_DeleteActorEgressPolicy_FullMethodName = "/ateapi.Control/DeleteActorEgressPolicy" + Control_MintActorJWT_FullMethodName = "/ateapi.Control/MintActorJWT" + Control_MintActorCertificate_FullMethodName = "/ateapi.Control/MintActorCertificate" Control_CreateTag_FullMethodName = "/ateapi.Control/CreateTag" Control_GetTag_FullMethodName = "/ateapi.Control/GetTag" Control_ListTags_FullMethodName = "/ateapi.Control/ListTags" @@ -97,6 +99,18 @@ type ControlClient interface { UpdateActorEgressPolicy(ctx context.Context, in *UpdateActorEgressPolicyRequest, opts ...grpc.CallOption) (*EgressPolicy, error) // Delete the egress policy resource nested under an Actor. DeleteActorEgressPolicy(ctx context.Context, in *DeleteActorEgressPolicyRequest, opts ...grpc.CallOption) (*EgressPolicy, error) + // Create a Substrate-issued JWT asserting the actor identity. + // + // * Called by the egress gateway when actor JWT injection is configured for outbound requests. + MintActorJWT(ctx context.Context, in *MintActorJWTRequest, opts ...grpc.CallOption) (*MintActorJWTResponse, error) + // Create a Substrate-issued SPIFFE certificate asserting the actor identity. + // + // * Called by atelet to provision an atunnel with a certificate for + // communication with the egress gateway. TODO(ahmedtd): Migrate this use + // case to a distinct certificate to prevent actor/atunnel confusion. + // * Called by the egress gateway when actor client certificate injection is + // configured for outbound requests. + MintActorCertificate(ctx context.Context, in *MintActorCertificateRequest, opts ...grpc.CallOption) (*MintActorCertificateResponse, error) // Tag the external snapshot a suspended Actor holds. The tag gets its own // copy of that snapshot, so suspending or deleting the Actor afterwards // cannot collect it. @@ -265,6 +279,26 @@ func (c *controlClient) DeleteActorEgressPolicy(ctx context.Context, in *DeleteA return out, nil } +func (c *controlClient) MintActorJWT(ctx context.Context, in *MintActorJWTRequest, opts ...grpc.CallOption) (*MintActorJWTResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MintActorJWTResponse) + err := c.cc.Invoke(ctx, Control_MintActorJWT_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *controlClient) MintActorCertificate(ctx context.Context, in *MintActorCertificateRequest, opts ...grpc.CallOption) (*MintActorCertificateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MintActorCertificateResponse) + err := c.cc.Invoke(ctx, Control_MintActorCertificate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *controlClient) CreateTag(ctx context.Context, in *CreateTagRequest, opts ...grpc.CallOption) (*Tag, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Tag) @@ -505,6 +539,18 @@ type ControlServer interface { UpdateActorEgressPolicy(context.Context, *UpdateActorEgressPolicyRequest) (*EgressPolicy, error) // Delete the egress policy resource nested under an Actor. DeleteActorEgressPolicy(context.Context, *DeleteActorEgressPolicyRequest) (*EgressPolicy, error) + // Create a Substrate-issued JWT asserting the actor identity. + // + // * Called by the egress gateway when actor JWT injection is configured for outbound requests. + MintActorJWT(context.Context, *MintActorJWTRequest) (*MintActorJWTResponse, error) + // Create a Substrate-issued SPIFFE certificate asserting the actor identity. + // + // * Called by atelet to provision an atunnel with a certificate for + // communication with the egress gateway. TODO(ahmedtd): Migrate this use + // case to a distinct certificate to prevent actor/atunnel confusion. + // * Called by the egress gateway when actor client certificate injection is + // configured for outbound requests. + MintActorCertificate(context.Context, *MintActorCertificateRequest) (*MintActorCertificateResponse, error) // Tag the external snapshot a suspended Actor holds. The tag gets its own // copy of that snapshot, so suspending or deleting the Actor afterwards // cannot collect it. @@ -596,6 +642,12 @@ func (UnimplementedControlServer) UpdateActorEgressPolicy(context.Context, *Upda func (UnimplementedControlServer) DeleteActorEgressPolicy(context.Context, *DeleteActorEgressPolicyRequest) (*EgressPolicy, error) { return nil, status.Error(codes.Unimplemented, "method DeleteActorEgressPolicy not implemented") } +func (UnimplementedControlServer) MintActorJWT(context.Context, *MintActorJWTRequest) (*MintActorJWTResponse, error) { + return nil, status.Error(codes.Unimplemented, "method MintActorJWT not implemented") +} +func (UnimplementedControlServer) MintActorCertificate(context.Context, *MintActorCertificateRequest) (*MintActorCertificateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method MintActorCertificate not implemented") +} func (UnimplementedControlServer) CreateTag(context.Context, *CreateTagRequest) (*Tag, error) { return nil, status.Error(codes.Unimplemented, "method CreateTag not implemented") } @@ -878,6 +930,42 @@ func _Control_DeleteActorEgressPolicy_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _Control_MintActorJWT_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MintActorJWTRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServer).MintActorJWT(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Control_MintActorJWT_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServer).MintActorJWT(ctx, req.(*MintActorJWTRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Control_MintActorCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MintActorCertificateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServer).MintActorCertificate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Control_MintActorCertificate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServer).MintActorCertificate(ctx, req.(*MintActorCertificateRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Control_CreateTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreateTagRequest) if err := dec(in); err != nil { @@ -1307,6 +1395,14 @@ var Control_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeleteActorEgressPolicy", Handler: _Control_DeleteActorEgressPolicy_Handler, }, + { + MethodName: "MintActorJWT", + Handler: _Control_MintActorJWT_Handler, + }, + { + MethodName: "MintActorCertificate", + Handler: _Control_MintActorCertificate_Handler, + }, { MethodName: "CreateTag", Handler: _Control_CreateTag_Handler, @@ -1396,190 +1492,6 @@ var Control_ServiceDesc = grpc.ServiceDesc{ Metadata: "ateapi.proto", } -const ( - ActorIdentity_MintJWT_FullMethodName = "/ateapi.ActorIdentity/MintJWT" - ActorIdentity_MintCert_FullMethodName = "/ateapi.ActorIdentity/MintCert" -) - -// ActorIdentityClient is the client API for ActorIdentity service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -// -// ActorIdentity allows substrate workloads to exchange their -// infrastructure-level credentials (k8s service account token, etc.) for a -// substrate actor-level credential. A given substrate actor might migrate -// between many different physical workers over the course of its lifecycle, -// whereas the actor credential's identity will be stable for the life of the -// actor. -type ActorIdentityClient interface { - // Request an Actor Identity JWT. - // - // To call this RPC, you must be authenticated as the Kubernetes Pod that is - // currently running the requested actor. - MintJWT(ctx context.Context, in *MintJWTRequest, opts ...grpc.CallOption) (*MintJWTResponse, error) - // Request an Actor Identity Certificate for an actor. - // - // Actors do not call this RPC themselves. The atelet hosting the actor calls - // it on the actor's behalf, authenticating with its own client certificate - // rather than a bearer token. - // - // Authorization is decided on that client certificate and the worker - // identity attested by atelet. Ateapi verifies that the worker is assigned to - // the actor and that the actor points back to that exact worker before signing. - // - // The certificate in the response is the actor's identity, not the atelet's. - MintCert(ctx context.Context, in *MintCertRequest, opts ...grpc.CallOption) (*MintCertResponse, error) -} - -type actorIdentityClient struct { - cc grpc.ClientConnInterface -} - -func NewActorIdentityClient(cc grpc.ClientConnInterface) ActorIdentityClient { - return &actorIdentityClient{cc} -} - -func (c *actorIdentityClient) MintJWT(ctx context.Context, in *MintJWTRequest, opts ...grpc.CallOption) (*MintJWTResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(MintJWTResponse) - err := c.cc.Invoke(ctx, ActorIdentity_MintJWT_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *actorIdentityClient) MintCert(ctx context.Context, in *MintCertRequest, opts ...grpc.CallOption) (*MintCertResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(MintCertResponse) - err := c.cc.Invoke(ctx, ActorIdentity_MintCert_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ActorIdentityServer is the server API for ActorIdentity service. -// All implementations must embed UnimplementedActorIdentityServer -// for forward compatibility. -// -// ActorIdentity allows substrate workloads to exchange their -// infrastructure-level credentials (k8s service account token, etc.) for a -// substrate actor-level credential. A given substrate actor might migrate -// between many different physical workers over the course of its lifecycle, -// whereas the actor credential's identity will be stable for the life of the -// actor. -type ActorIdentityServer interface { - // Request an Actor Identity JWT. - // - // To call this RPC, you must be authenticated as the Kubernetes Pod that is - // currently running the requested actor. - MintJWT(context.Context, *MintJWTRequest) (*MintJWTResponse, error) - // Request an Actor Identity Certificate for an actor. - // - // Actors do not call this RPC themselves. The atelet hosting the actor calls - // it on the actor's behalf, authenticating with its own client certificate - // rather than a bearer token. - // - // Authorization is decided on that client certificate and the worker - // identity attested by atelet. Ateapi verifies that the worker is assigned to - // the actor and that the actor points back to that exact worker before signing. - // - // The certificate in the response is the actor's identity, not the atelet's. - MintCert(context.Context, *MintCertRequest) (*MintCertResponse, error) - mustEmbedUnimplementedActorIdentityServer() -} - -// UnimplementedActorIdentityServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedActorIdentityServer struct{} - -func (UnimplementedActorIdentityServer) MintJWT(context.Context, *MintJWTRequest) (*MintJWTResponse, error) { - return nil, status.Error(codes.Unimplemented, "method MintJWT not implemented") -} -func (UnimplementedActorIdentityServer) MintCert(context.Context, *MintCertRequest) (*MintCertResponse, error) { - return nil, status.Error(codes.Unimplemented, "method MintCert not implemented") -} -func (UnimplementedActorIdentityServer) mustEmbedUnimplementedActorIdentityServer() {} -func (UnimplementedActorIdentityServer) testEmbeddedByValue() {} - -// UnsafeActorIdentityServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ActorIdentityServer will -// result in compilation errors. -type UnsafeActorIdentityServer interface { - mustEmbedUnimplementedActorIdentityServer() -} - -func RegisterActorIdentityServer(s grpc.ServiceRegistrar, srv ActorIdentityServer) { - // If the following call panics, it indicates UnimplementedActorIdentityServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&ActorIdentity_ServiceDesc, srv) -} - -func _ActorIdentity_MintJWT_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MintJWTRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ActorIdentityServer).MintJWT(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ActorIdentity_MintJWT_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ActorIdentityServer).MintJWT(ctx, req.(*MintJWTRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ActorIdentity_MintCert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MintCertRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ActorIdentityServer).MintCert(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ActorIdentity_MintCert_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ActorIdentityServer).MintCert(ctx, req.(*MintCertRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// ActorIdentity_ServiceDesc is the grpc.ServiceDesc for ActorIdentity service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var ActorIdentity_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "ateapi.ActorIdentity", - HandlerType: (*ActorIdentityServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "MintJWT", - Handler: _ActorIdentity_MintJWT_Handler, - }, - { - MethodName: "MintCert", - Handler: _ActorIdentity_MintCert_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "ateapi.proto", -} - const ( WorkerService_SetWorkerCapacity_FullMethodName = "/ateapi.WorkerService/SetWorkerCapacity" )