Security#

PyPIM involves gRPC connections at two different points, and each can be secured independently:

  1. The connection from the PyPIM client to the PIM server itself, used for list_definitions(), create_instance(), and the other PIM API calls.

  2. The connection from the PyPIM client to a product instance started by the PIM server. The transport used for this connection is decided by the PIM server and communicated back to the client through the created Instance.

Both connections support the same set of transports:

  • insecure: no transport security. This is the default.

  • tls: TLS encryption. Used only for the client-to-PIM-server connection, authenticated with a bearer token rather than a client certificate.

  • mtls: mutual TLS, authenticated with a client certificate, a private key, and a certificate authority (CA) file.

  • uds: Unix Domain Socket. Only available on platforms and gRPC versions that support it.

  • wnua: Windows Named User Authentication. Windows only, and only for local connections.

Securing the client-to-PIM-server connection#

This is the connection created by connect(). It can be configured either through a configuration file or programmatically.

Configuration file#

The configuration file referenced by the ANSYS_PLATFORM_INSTANCEMANAGEMENT_CONFIG environment variable has two supported formats.

Version 1 (tls flag)#

The original format supports only insecure and tls:

{
    "version": 1,
    "pim": {
        "uri": "dns:pim.svc.com:80",
        "headers": {
            "authorization": "Bearer <token>"
        },
        "tls": true
    }
}

When tls is true, the authorization header must contain a Bearer token, which is used to build the secure channel credentials. This format continues to work unchanged; it is never removed by adding version 2 support.

Version 2 (security block)#

The version 2 format replaces the tls flag with a security block that can select any of the five transports:

{
    "version": 2,
    "pim": {
        "uri": "dns:pim.svc.com:80",
        "headers": {
            "metadata-info": "value"
        },
        "security": {
            "transport": "mtls",
            "certificate_files": {
                "cert_file": "client.crt",
                "key_file": "client.key",
                "ca_file": "ca.crt"
            }
        }
    }
}

The security.transport value must be one of insecure, tls, uds, mtls, or wnua.

  • For tls, the authorization header must still contain a Bearer token, exactly as in version 1.

  • For mtls, provide client certificates either as individual files with certificate_files (cert_file, key_file, and ca_file, all required together) or as a certificates_directory path. Provide at most one of the two; providing neither is valid and lets the underlying transport layer resolve its own defaults.

  • For uds, the socket path is taken directly from a unix: uri. Because the PIM server is expected to already be listening on that socket, the socket path is checked for existence when the configuration is loaded.

  • For wnua and insecure, no additional security fields are needed.

Any invalid configuration (an unknown transport, conflicting mTLS certificate options, a missing UDS socket, or a missing bearer token for tls) raises InvalidConfigurationError as soon as the configuration is loaded, rather than failing later on the first request.

Programmatic configuration#

When no configuration file is present, connect() accepts the connection settings directly:

import ansys.platform.instancemanagement as pypim
from ansys.platform.instancemanagement import ConnectionSecurity
from ansys.tools.common.cyberchannel import CertificateFiles

client = pypim.connect(
    uri="dns:pim.svc.com:80",
    headers={"identity": "james"},
    security=ConnectionSecurity(
        transport="mtls",
        cert_files=CertificateFiles(
            cert_file="client.crt", key_file="client.key", ca_file="ca.crt"
        ),
    ),
)

ConnectionSecurity mirrors the version 2 configuration file’s security options. Its transport is one of insecure, tls, uds, mtls, or wnua. For mtls, provide client certificates as either cert_files (a CertificateFiles) or certs_dir (a directory path), but not both; providing neither is valid and lets the underlying transport layer resolve its own defaults. An invalid combination raises ValueError at construction time.

Precedence between the configuration file and these parameters is file-exclusive and all-or-nothing: if the environment is configured with a configuration file (is_configured() is True), that file is used in full and the uri, headers, and security parameters are ignored entirely. The parameters are used only when there is no configuration file. If neither a file nor a uri parameter is available, connect() raises NotConfiguredError.

Securing a product instance at creation#

Client.create_instance() accepts an optional security_settings parameter describing which transport the PIM server should use to expose the created instance:

import ansys.platform.instancemanagement as pypim
from ansys.platform.instancemanagement import MtlsSettings

client = pypim.connect()
instance = client.create_instance(
    product_name="mapdl",
    security_settings=MtlsSettings(certificates_directory="/path/to/certs"),
)

security_settings accepts one of:

  • InsecureSettings: no transport security (the default when security_settings is not provided; the actual choice is up to the PIM server).

  • MtlsSettings: mutual TLS, with certificates provided either as a certificates_directory or as individual certificate_paths (MtlsCertificatePaths).

  • UdsSettings: Unix Domain Socket, with the socket location provided either as a full socket_path or as a socket_directory / socket_identifier pair.

  • WnuaSettings: Windows Named User Authentication. Windows only.

For MtlsSettings and UdsSettings, supplying a conflicting combination of options (for example both certificate sources, or a socket_path together with a socket_directory) raises ValueError at construction time.

These settings are a request: the PIM server ultimately decides what transport the instance is actually exposed with, and communicates that choice back through the instance’s services (see the next section).

Reading security settings from a service#

Once an instance is ready, each entry in Instance.services is a Service describing how to reach that particular endpoint. When the server reports transport security for a service, Service.transport exposes the resolved transport name (one of insecure, uds, mtls, or wnua), or None when the server did not report any security information for that service:

instance.wait_for_ready()
service = instance.services["grpc"]
print(service.transport)  # e.g. "mtls"

You do not need to build the gRPC channel yourself based on this information: Instance.build_grpc_channel() reads the resolved transport (and, for mtls, the client certificate files) and builds a correctly secured channel automatically.