nethsm_config

Struct HermeticParallelConfig

Source
pub struct HermeticParallelConfig {
    iteration: u32,
    admin_secret_handling: AdministrativeSecretHandling,
    non_admin_secret_handling: NonAdministrativeSecretHandling,
    connections: HashSet<Connection>,
    users: HashSet<UserMapping>,
    settings: ConfigSettings,
}
Expand description

A configuration for parallel use of connections with a set of system and NetHSM users.

This configuration type is meant to be used in a read-only fashion and does not support tracking the passphrases for users. As such, it is useful for tools, that create system users, as well as NetHSM users and keys according to it.

Various mappings of system and [NetHsm] users exist, that are defined by the variants of UserMapping.

Some system users require providing SSH authorized key(s), while others do not allow that at all. NetHSM users can be added in namespaces, or system-wide, depending on their use-case. System and NetHSM users must be unique.

Key IDs must be unique per namespace or system-wide (depending on where they are used). Tags, used to provide access to keys for NetHSM users must be unique per namespace or system-wide (depending on in which scope the user and key are used)

§Examples

The below example provides a fully functional TOML configuration, outlining all available functionalities.

let config_string = r#"
# A non-negative integer, that describes the iteration of the configuration.
# The iteration should only ever be increased between changes to the config and only under the circumstance,
# that user mappings are removed and should also be removed from the state of the system making use of this
# configuration.
# Applications reading the configuration are thereby enabled to compare existing state on the system with the
# current iteration and remove user mappings and accompanying data accordingly.
iteration = 1

# The handling of administrative secrets on the system.
# One of:
# - "shamirs-secret-sharing": Administrative secrets are never persisted on the system and only provided as shares of a shared secret.
# - "systemd-creds": Administrative secrets are persisted on the system as host-specific files, encrypted using systemd-creds (only for testing).
# - "plaintext": Administrative secrets are persisted on the system in unencrypted plaintext files (only for testing).
admin_secret_handling = "shamirs-secret-sharing"

# The handling of non-administrative secrets on the system.
# One of:
# - "systemd-creds": Non-administrative secrets are persisted on the system as host-specific files, encrypted using systemd-creds (the default).
# - "plaintext": Non-administrative secrets are persisted on the system in unencrypted plaintext files (only for testing).
non_admin_secret_handling = "systemd-creds"

[[connections]]
url = "https://localhost:8443/api/v1/"
tls_security = "Unsafe"

# The NetHSM user "admin" is a system-wide Administrator
[[users]]
nethsm_only_admin = "admin"

# The SSH-accessible system user "ssh-backup1" is used in conjunction with
# the NetHSM user "backup1" (system-wide Backup)
[[users]]

[users.system_nethsm_backup]
nethsm_user = "backup1"
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host"
system_user = "ssh-backup1"

# The SSH-accessible system user "ssh-metrics1" is used with several NetHSM users:
# - "metrics1" (system-wide Metrics)
# - "keymetrics1" (system-wide Operator)
# - "ns1~keymetrics1" (namespace Operator)
[[users]]

[users.system_nethsm_metrics]
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host"
system_user = "ssh-metrics1"

[users.system_nethsm_metrics.nethsm_users]
metrics_user = "metrics1"
operator_users = ["keymetrics1", "ns1~keymetrics1"]

# The SSH-accessible system user "ssh-operator1" is used in conjunction with
# the NetHSM user "operator1" (system-wide Operator).
# User "operator1" shares tag "tag1" with key "key1" and can therefore use it
# (for OpenPGP signing).
[[users]]

[users.system_nethsm_operator_signing]
nethsm_user = "operator1"
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host"
system_user = "ssh-operator1"
tag = "tag1"

[users.system_nethsm_operator_signing.nethsm_key_setup]
key_id = "key1"
key_type = "Curve25519"
key_mechanisms = ["EdDsaSignature"]
signature_type = "EdDsa"

[users.system_nethsm_operator_signing.nethsm_key_setup.key_context.openpgp]
user_ids = ["Foobar McFooface <foobar@mcfooface.org>"]
version = "4"

# The SSH-accessible system user "ssh-operator2" is used in conjunction with
# the NetHSM user "operator2" (system-wide Operator).
# User "operator2" shares tag "tag2" with key "key2" and can therefore use it
# (for OpenPGP signing).
[[users]]

[users.system_nethsm_operator_signing]
nethsm_user = "operator2"
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host"
system_user = "ssh-operator2"
tag = "tag2"

[users.system_nethsm_operator_signing.nethsm_key_setup]
key_id = "key2"
key_type = "Curve25519"
key_mechanisms = ["EdDsaSignature"]
signature_type = "EdDsa"

[users.system_nethsm_operator_signing.nethsm_key_setup.key_context.openpgp]
user_ids = ["Foobar McFooface <foobar@mcfooface.org>"]
version = "4"

# The NetHSM user "ns1~admin" is a namespace Administrator
[[users]]
nethsm_only_admin = "ns1~admin"

# The SSH-accessible system user "ns1-ssh-operator1" is used in conjunction with
# the NetHSM user "ns1~operator1" (namespace Operator).
# User "ns1~operator1" shares tag "tag1" with key "key1" and can therefore use it
# in its namespace (for OpenPGP signing).
[[users]]

[users.system_nethsm_operator_signing]
nethsm_user = "ns1~operator1"
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host"
system_user = "ns1-ssh-operator1"
tag = "tag1"

[users.system_nethsm_operator_signing.nethsm_key_setup]
key_id = "key1"
key_type = "Curve25519"
key_mechanisms = ["EdDsaSignature"]
signature_type = "EdDsa"

[users.system_nethsm_operator_signing.nethsm_key_setup.key_context.openpgp]
user_ids = ["Foobar McFooface <foobar@mcfooface.org>"]
version = "4"

# The SSH-accessible system user "ns1-ssh-operator2" is used in conjunction with
# the NetHSM user "ns2~operator1" (namespace Operator).
# User "ns1~operator2" shares tag "tag2" with key "key1" and can therefore use it
# in its namespace (for OpenPGP signing).
[[users]]

[users.system_nethsm_operator_signing]
nethsm_user = "ns1~operator2"
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIYA+bfMBThUP5lKbMFEHiytmcCPhpkGrB/85n0mAN user@host"
system_user = "ns1-ssh-operator2"
tag = "tag2"

[users.system_nethsm_operator_signing.nethsm_key_setup]
key_id = "key2"
key_type = "Curve25519"
key_mechanisms = ["EdDsaSignature"]
signature_type = "EdDsa"

[users.system_nethsm_operator_signing.nethsm_key_setup.key_context.openpgp]
user_ids = ["Foobar McFooface <foobar@mcfooface.org>"]
version = "4"

# The hermetic system user "local-metrics1" is used with several NetHSM users:
# - "metrics2" (system-wide Metrics)
# - "keymetrics2" (system-wide Operator)
# - "ns1~keymetrics2" (namespace Operator)
[[users]]

[users.hermetic_system_nethsm_metrics]
system_user = "local-metrics1"

[users.hermetic_system_nethsm_metrics.nethsm_users]
metrics_user = "metrics2"
operator_users = ["keymetrics2", "ns1~keymetrics2"]

# The SSH-accessible system user "ssh-share-down" is used for the
# download of shares of a shared secret (divided by Shamir's Secret Sharing).
[[users]]

[users.system_only_share_download]
ssh_authorized_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host"]
system_user = "ssh-share-down"

# The SSH-accessible system user "ssh-share-up" is used for the
# upload of shares of a shared secret (divided by Shamir's Secret Sharing).
[[users]]

[users.system_only_share_upload]
ssh_authorized_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host"]
system_user = "ssh-share-up"

# The SSH-accessible system user "ssh-wireguard-down" is used for the
# download of WireGuard configuration, used on the host.
[[users]]

[users.system_only_wireguard_download]
ssh_authorized_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host"]
system_user = "ssh-wireguard-down"
"#;

Fields§

§iteration: u32§admin_secret_handling: AdministrativeSecretHandling§non_admin_secret_handling: NonAdministrativeSecretHandling§connections: HashSet<Connection>§users: HashSet<UserMapping>§settings: ConfigSettings

Implementations§

Source§

impl HermeticParallelConfig

Source

pub fn new_from_file( config_settings: ConfigSettings, path: Option<&Path>, ) -> Result<Self, Error>

Creates a new HermeticParallelConfig from a configuration file.

§Errors

Returns an error if the configuration file can not be loaded.

§Examples

use nethsm_config::{ConfigInteractivity, ConfigName, ConfigSettings, HermeticParallelConfig};

let config_file = testdir::testdir!().join("basic_parallel_config_new.conf");
{
    #[rustfmt::skip]
    let config_string = r#"
iteration = 1
admin_secret_handling = "shamirs-secret-sharing"
non_admin_secret_handling = "systemd-creds"
[[connections]]
url = "https://localhost:8443/api/v1/"
tls_security = "Unsafe"

[[users]]
nethsm_only_admin = "admin"

[[users]]
[users.system_nethsm_backup]
nethsm_user = "backup1"
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host"
system_user = "ssh-backup1"

[[users]]

[users.system_nethsm_metrics]
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host"
system_user = "ssh-metrics1"

[users.system_nethsm_metrics.nethsm_users]
metrics_user = "metrics1"
operator_users = ["operator1metrics1"]

[[users]]
[users.system_nethsm_operator_signing]
nethsm_user = "operator1"
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host"
system_user = "ssh-operator1"
tag = "tag1"

[users.system_nethsm_operator_signing.nethsm_key_setup]
key_id = "key1"
key_type = "Curve25519"
key_mechanisms = ["EdDsaSignature"]
signature_type = "EdDsa"

[users.system_nethsm_operator_signing.nethsm_key_setup.key_context.openpgp]
user_ids = ["Foobar McFooface <foobar@mcfooface.org>"]
version = "4"

[[users]]
[users.system_nethsm_operator_signing]
nethsm_user = "operator2"
ssh_authorized_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host"
system_user = "ssh-operator2"
tag = "tag2"

[users.system_nethsm_operator_signing.nethsm_key_setup]
key_id = "key2"
key_type = "Curve25519"
key_mechanisms = ["EdDsaSignature"]
signature_type = "EdDsa"

[users.system_nethsm_operator_signing.nethsm_key_setup.key_context.openpgp]
user_ids = ["Foobar McFooface <foobar@mcfooface.org>"]
version = "4"

[[users]]

[users.hermetic_system_nethsm_metrics]
system_user = "local-metrics1"

[users.hermetic_system_nethsm_metrics.nethsm_users]
metrics_user = "metrics2"
operator_users = ["operator2metrics1"]

[[users]]
[users.system_only_share_download]
ssh_authorized_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host"]
system_user = "ssh-share-down"

[[users]]
[users.system_only_share_upload]
ssh_authorized_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host"]
system_user = "ssh-share-up"

[[users]]
[users.system_only_wireguard_download]
ssh_authorized_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host"]
system_user = "ssh-wireguard-down"
"#;
    let mut buffer = std::fs::File::create(&config_file)?;
    buffer.write_all(config_string.as_bytes())?;
}
HermeticParallelConfig::new_from_file(
    ConfigSettings::new(
        "my_app".to_string(),
        ConfigInteractivity::NonInteractive,
        None,
    ),
    Some(&config_file),
)?;
Source

pub fn new( config_settings: ConfigSettings, iteration: u32, admin_secret_handling: AdministrativeSecretHandling, non_admin_secret_handling: NonAdministrativeSecretHandling, connections: HashSet<Connection>, users: HashSet<UserMapping>, ) -> Result<Self, Error>

Creates a new HermeticParallelConfig.

§Errors

Returns an error if the configuration file can not be loaded.

§Examples
use std::collections::HashSet;

use nethsm::UserRole;
use nethsm_config::{
    AdministrativeSecretHandling,
    AuthorizedKeyEntryList,
    ConfigCredentials,
    ConfigInteractivity,
    ConfigName,
    ConfigSettings,
    Connection,
    HermeticParallelConfig,
    NonAdministrativeSecretHandling,
    UserMapping,
};

HermeticParallelConfig::new(
    ConfigSettings::new(
        "my_app".to_string(),
        ConfigInteractivity::NonInteractive,
        None,
    ),
    1,
    AdministrativeSecretHandling::ShamirsSecretSharing,
    NonAdministrativeSecretHandling::SystemdCreds,
    HashSet::from([Connection::new(
        "https://localhost:8443/api/v1/".parse()?,
        "Unsafe".parse()?,
    )]),
    HashSet::from([
        UserMapping::NetHsmOnlyAdmin("admin".parse()?),
        UserMapping::SystemOnlyShareDownload {
            system_user: "ssh-share-down".parse()?,
            ssh_authorized_keys: AuthorizedKeyEntryList::new(vec!["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?])?,
        },
        UserMapping::SystemOnlyShareUpload {
            system_user: "ssh-share-up".parse()?,
            ssh_authorized_keys: AuthorizedKeyEntryList::new(vec!["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?])?,
        }]),
)?;
Source

pub fn store(&self, path: Option<&Path>) -> Result<(), Error>

Writes a HermeticParallelConfig to file.

§Errors

Returns an error if the configuration file can not be written.

§Examples
use std::collections::HashSet;

use nethsm::{CryptographicKeyContext, OpenPgpUserIdList, SigningKeySetup, UserRole};
use nethsm_config::{
    AuthorizedKeyEntryList,
    AdministrativeSecretHandling,
    ConfigCredentials,
    ConfigInteractivity,
    ConfigName,
    ConfigSettings,
    Connection,
    HermeticParallelConfig,
    NetHsmMetricsUsers,
    NonAdministrativeSecretHandling,
    UserMapping,
};

let config = HermeticParallelConfig::new(
    ConfigSettings::new(
        "my_app".to_string(),
        ConfigInteractivity::NonInteractive,
        None,
    ),
    1,
    AdministrativeSecretHandling::ShamirsSecretSharing,
    NonAdministrativeSecretHandling::SystemdCreds,
    HashSet::from([Connection::new(
        "https://localhost:8443/api/v1/".parse()?,
        "Unsafe".parse()?,
    )]),
    HashSet::from([UserMapping::NetHsmOnlyAdmin("admin".parse()?),
        UserMapping::SystemNetHsmBackup {
            nethsm_user: "backup1".parse()?,
            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
            system_user: "ssh-backup1".parse()?,
        },
        UserMapping::SystemNetHsmMetrics {
            nethsm_users: NetHsmMetricsUsers::new("metrics1".parse()?, vec!["operator2metrics1".parse()?])?,
            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIioJ9uvAxUPunFh89T+ENo7OQerqHE8SQ+2v4VWbfUZ user@host".parse()?,
            system_user: "ssh-metrics1".parse()?,
        },
        UserMapping::SystemNetHsmOperatorSigning {
            nethsm_user: "operator1".parse()?,
            nethsm_key_setup: SigningKeySetup::new(
                "key1".parse()?,
                "Curve25519".parse()?,
                vec!["EdDsaSignature".parse()?],
                None,
                "EdDsa".parse()?,
                CryptographicKeyContext::OpenPgp{
                    user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
                    version: "4".parse()?,
                })?,
            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
            system_user: "ssh-operator1".parse()?,
            tag: "tag1".to_string(),
        },
        UserMapping::HermeticSystemNetHsmMetrics {
            nethsm_users: NetHsmMetricsUsers::new("metrics2".parse()?, vec!["operator1metrics1".parse()?])?,
            system_user: "local-metrics1".parse()?,
        },
        UserMapping::SystemOnlyShareDownload {
            system_user: "ssh-share-down".parse()?,
            ssh_authorized_keys: AuthorizedKeyEntryList::new(
                vec!["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?],
            )?,
        },
        UserMapping::SystemOnlyShareUpload {
            system_user: "ssh-share-up".parse()?,
            ssh_authorized_keys: AuthorizedKeyEntryList::new(
                vec!["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?],
            )?,
        },
        UserMapping::SystemOnlyWireGuardDownload {
            system_user: "ssh-wireguard-down".parse()?,
            ssh_authorized_keys: AuthorizedKeyEntryList::new(
                vec!["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?],
            )?,
        },
    ]),
)?;

let config_file = testdir::testdir!().join("basic_parallel_config_store.conf");
config.store(Some(&config_file))?;
Source

pub fn iter_connections(&self) -> impl Iterator<Item = &Connection>

Returns an Iterator over the available Connections.

Source

pub fn iter_user_mappings(&self) -> impl Iterator<Item = &UserMapping>

Returns an Iterator over the available UserMappings.

Source

fn validate(&self) -> Result<(), Error>

Validates the components of the HermeticParallelConfig.

Trait Implementations§

Source§

impl Clone for HermeticParallelConfig

Source§

fn clone(&self) -> HermeticParallelConfig

Returns a copy of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for HermeticParallelConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for HermeticParallelConfig

Source§

fn default() -> HermeticParallelConfig

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for HermeticParallelConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for HermeticParallelConfig

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dst: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dst. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

§

impl<T> ErasedDestructor for T
where T: 'static,

§

impl<T> MaybeSendSync for T