Connect to offline environments

Configure the Lattice SDK to connect at the edge

By default, the Lattice SDK validates TLS certificates against your operating system’s trusted Certificate Authority (CA) store.

When your Lattice environment is offline or air-gapped, it’s deployed on a local network without access to a public CA infrastructure. The environment presents a certificate signed by a private Anduril CA that isn’t in your system’s trust store, so your SDK client can’t validate it and the connection fails.

This guide describes two ways to connect:

  • Use an Anduril-issued certificate because it lets the SDK trust your environment while keeping full TLS verification enabled. This is the recommended approach.
  • Skip TLS verification with self-signed certificates because it lets you connect when an Anduril representative can’t issue a certificate. Use this approach only in development environments, because it disables the protections that TLS provides.

Before you begin

  • Complete the steps in Set up to configure your environment variables: LATTICE_ENDPOINT, LATTICE_CLIENT_ID, and LATTICE_CLIENT_SECRET.
gRPC authentication

If you are using gRPC with client credentials, set up the token refresh module before running the examples on this page.

Use an Anduril-issued certificate

This is the recommended approach because it keeps full TLS verification enabled. You obtain a CA certificate from Anduril, add it to your trust store, and configure the SDK to trust it as a root CA. The SDK still validates the certificate chain, hostname, and expiry date.

Before you configure your client, complete the following setup:

  • Contact your Anduril representative to obtain the CA certificate for your Lattice environment. The certificate is delivered as a PEM-encoded file. Because delivery mechanisms vary, follow the instructions your Anduril representative provides to retrieve it.

  • Add the certificate to your operating system’s trust store so that clients on your machine trust it:

    $sudo security add-trusted-cert -d -r trustRoot \
    > -k /Library/Keychains/System.keychain /path/to/anduril-ca.pem
  • Set a LATTICE_CA_CERT_PATH environment variable that points to the certificate file:

    $export LATTICE_CA_CERT_PATH=/path/to/anduril-ca.pem

The following examples load the CA certificate from LATTICE_CA_CERT_PATH and configure the Lattice client to trust it as a root CA. Full TLS verification stays enabled, so the SDK still validates the certificate chain, hostname, and expiry date.

1

Initialize the client

Import your environment variables and initialize the client. LATTICE_CA_CERT_PATH is the file path to the PEM certificate you received from your Anduril representative. You can achieve the same result using a local .env file:

1package main
2
3import (
4 "context"
5 "crypto/tls"
6 "crypto/x509"
7 "fmt"
8 "net/http"
9 "os"
10
11 Lattice "github.com/anduril/lattice-sdk-go/v4"
12 "github.com/anduril/lattice-sdk-go/v4/client"
13 "github.com/anduril/lattice-sdk-go/v4/option"
14)
15
16func main() {
17 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
18 clientID := os.Getenv("LATTICE_CLIENT_ID")
19 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
20 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
21
22 // Path to the Anduril-issued CA certificate in PEM format
23 caCertPath := os.Getenv("LATTICE_CA_CERT_PATH")
24
25 // Initialize the client
26 LatticeClient, err := createLatticeClient(latticeEndpoint, clientID, clientSecret,
27 sandboxesToken,
28 caCertPath,
29 )
30 if err != nil {
31 fmt.Printf("Error creating Lattice client: %v\n", err)
32 os.Exit(1)
33 }
34
35 // Use the client
36 ctx := context.Background()
37 entity, err := LatticeClient.Entities.GetEntity(
38 ctx,
39 &Lattice.GetEntityRequest{
40 EntityID: "Demo-Sim-Asset1",
41 },
42 )
43
44 if err != nil {
45 fmt.Printf("Error fetching entity: %v\n", err)
46 } else {
47 fmt.Printf("Asset name | %s\n", *entity.GetAliases().GetName())
48 fmt.Printf("Asset location | %f, %f\n", *entity.GetLocation().GetPosition().GetLatitudeDegrees(),
49 *entity.GetLocation().GetPosition().GetLongitudeDegrees())
50 }
51}
52
53func createLatticeClient(endpoint string, clientID string, clientSecret string,
54 sandboxesToken string,
55 caCertPath string,
56) (*client.Client, error) {
57
58 headers := http.Header{}
59 if sandboxesToken != "" {
60 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
61 }
62
63 // Load the Anduril-issued CA certificate and add it to the trust pool
64 certPool, err := x509.SystemCertPool()
65 if err != nil {
66 certPool = x509.NewCertPool()
67 }
68 if caCertPath != "" {
69 pemBytes, err := os.ReadFile(caCertPath)
70 if err != nil {
71 return nil, fmt.Errorf("reading CA certificate: %w", err)
72 }
73 if !certPool.AppendCertsFromPEM(pemBytes) {
74 return nil, fmt.Errorf("failed to append CA certificate from %s", caCertPath)
75 }
76 }
77
78 httpClient := &http.Client{
79 Transport: &http.Transport{
80 TLSClientConfig: &tls.Config{
81 RootCAs: certPool,
82 },
83 },
84 }
85
86 latticeClient := client.NewClient(
87 option.WithClientCredentials(clientID, clientSecret),
88 option.WithBaseURL(fmt.Sprintf("https://%s", endpoint)),
89 option.WithHTTPHeader(headers),
90 option.WithHTTPClient(httpClient),
91 )
92
93 return latticeClient, nil
94}
2

Load the CA certificate

Configure the HTTP or gRPC client to trust the Anduril-issued certificate. The client loads the certificate from the PEM file and adds it to the trust pool, so the client verifies the server’s certificate against the Anduril CA during every TLS handshake:

1package main
2
3import (
4 "context"
5 "crypto/tls"
6 "crypto/x509"
7 "fmt"
8 "net/http"
9 "os"
10
11 Lattice "github.com/anduril/lattice-sdk-go/v4"
12 "github.com/anduril/lattice-sdk-go/v4/client"
13 "github.com/anduril/lattice-sdk-go/v4/option"
14)
15
16func main() {
17 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
18 clientID := os.Getenv("LATTICE_CLIENT_ID")
19 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
20 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
21
22 // Path to the Anduril-issued CA certificate in PEM format
23 caCertPath := os.Getenv("LATTICE_CA_CERT_PATH")
24
25 // Initialize the client
26 LatticeClient, err := createLatticeClient(latticeEndpoint, clientID, clientSecret,
27 sandboxesToken,
28 caCertPath,
29 )
30 if err != nil {
31 fmt.Printf("Error creating Lattice client: %v\n", err)
32 os.Exit(1)
33 }
34
35 // Use the client
36 ctx := context.Background()
37 entity, err := LatticeClient.Entities.GetEntity(
38 ctx,
39 &Lattice.GetEntityRequest{
40 EntityID: "Demo-Sim-Asset1",
41 },
42 )
43
44 if err != nil {
45 fmt.Printf("Error fetching entity: %v\n", err)
46 } else {
47 fmt.Printf("Asset name | %s\n", *entity.GetAliases().GetName())
48 fmt.Printf("Asset location | %f, %f\n", *entity.GetLocation().GetPosition().GetLatitudeDegrees(),
49 *entity.GetLocation().GetPosition().GetLongitudeDegrees())
50 }
51}
52
53func createLatticeClient(endpoint string, clientID string, clientSecret string,
54 sandboxesToken string,
55 caCertPath string,
56) (*client.Client, error) {
57
58 headers := http.Header{}
59 if sandboxesToken != "" {
60 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
61 }
62
63 // Load the Anduril-issued CA certificate and add it to the trust pool
64 certPool, err := x509.SystemCertPool()
65 if err != nil {
66 certPool = x509.NewCertPool()
67 }
68 if caCertPath != "" {
69 pemBytes, err := os.ReadFile(caCertPath)
70 if err != nil {
71 return nil, fmt.Errorf("reading CA certificate: %w", err)
72 }
73 if !certPool.AppendCertsFromPEM(pemBytes) {
74 return nil, fmt.Errorf("failed to append CA certificate from %s", caCertPath)
75 }
76 }
77
78 httpClient := &http.Client{
79 Transport: &http.Transport{
80 TLSClientConfig: &tls.Config{
81 RootCAs: certPool,
82 },
83 },
84 }
85
86 latticeClient := client.NewClient(
87 option.WithClientCredentials(clientID, clientSecret),
88 option.WithBaseURL(fmt.Sprintf("https://%s", endpoint)),
89 option.WithHTTPHeader(headers),
90 option.WithHTTPClient(httpClient),
91 )
92
93 return latticeClient, nil
94}
3

Use the client

Use the client to call the Lattice API. This example uses GetEntity
to fetch an entity by its entityId:

1package main
2
3import (
4 "context"
5 "crypto/tls"
6 "crypto/x509"
7 "fmt"
8 "net/http"
9 "os"
10
11 Lattice "github.com/anduril/lattice-sdk-go/v4"
12 "github.com/anduril/lattice-sdk-go/v4/client"
13 "github.com/anduril/lattice-sdk-go/v4/option"
14)
15
16func main() {
17 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
18 clientID := os.Getenv("LATTICE_CLIENT_ID")
19 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
20 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
21
22 // Path to the Anduril-issued CA certificate in PEM format
23 caCertPath := os.Getenv("LATTICE_CA_CERT_PATH")
24
25 // Initialize the client
26 LatticeClient, err := createLatticeClient(latticeEndpoint, clientID, clientSecret,
27 sandboxesToken,
28 caCertPath,
29 )
30 if err != nil {
31 fmt.Printf("Error creating Lattice client: %v\n", err)
32 os.Exit(1)
33 }
34
35 // Use the client
36 ctx := context.Background()
37 entity, err := LatticeClient.Entities.GetEntity(
38 ctx,
39 &Lattice.GetEntityRequest{
40 EntityID: "Demo-Sim-Asset1",
41 },
42 )
43
44 if err != nil {
45 fmt.Printf("Error fetching entity: %v\n", err)
46 } else {
47 fmt.Printf("Asset name | %s\n", *entity.GetAliases().GetName())
48 fmt.Printf("Asset location | %f, %f\n", *entity.GetLocation().GetPosition().GetLatitudeDegrees(),
49 *entity.GetLocation().GetPosition().GetLongitudeDegrees())
50 }
51}
52
53func createLatticeClient(endpoint string, clientID string, clientSecret string,
54 sandboxesToken string,
55 caCertPath string,
56) (*client.Client, error) {
57
58 headers := http.Header{}
59 if sandboxesToken != "" {
60 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
61 }
62
63 // Load the Anduril-issued CA certificate and add it to the trust pool
64 certPool, err := x509.SystemCertPool()
65 if err != nil {
66 certPool = x509.NewCertPool()
67 }
68 if caCertPath != "" {
69 pemBytes, err := os.ReadFile(caCertPath)
70 if err != nil {
71 return nil, fmt.Errorf("reading CA certificate: %w", err)
72 }
73 if !certPool.AppendCertsFromPEM(pemBytes) {
74 return nil, fmt.Errorf("failed to append CA certificate from %s", caCertPath)
75 }
76 }
77
78 httpClient := &http.Client{
79 Transport: &http.Transport{
80 TLSClientConfig: &tls.Config{
81 RootCAs: certPool,
82 },
83 },
84 }
85
86 latticeClient := client.NewClient(
87 option.WithClientCredentials(clientID, clientSecret),
88 option.WithBaseURL(fmt.Sprintf("https://%s", endpoint)),
89 option.WithHTTPHeader(headers),
90 option.WithHTTPClient(httpClient),
91 )
92
93 return latticeClient, nil
94}

After you add the CA certificate to your trust store and load it in your client, your integration can connect to an offline environment with full TLS verification enabled.

Skip TLS verification with self-signed certificates

Skipping TLS verification disables certificate validation, which exposes your connection to interception and tampering. Use this approach only in development environments where an Anduril representative can’t issue an Anduril-issued certificate. In production, use an Anduril-issued certificate instead.

If you can’t obtain an Anduril-issued certificate for a development environment, you can configure the SDK to skip TLS verification. The SDK then connects without validating the server’s certificate.

Before you configure your client, complete the following setup:

  • Set a SKIP_TLS_VERIFY environment variable so the client knows to skip certificate validation:

    $export SKIP_TLS_VERIFY=true

The following examples read SKIP_TLS_VERIFY and configure a custom Lattice client that skips TLS verification.

1

Initialize the client

Import your environment variables and initialize the client. SKIP_TLS_VERIFY controls whether the client validates the server’s certificate. You can achieve the same result using a local .env file:

1package main
2
3import (
4 "context"
5 "crypto/tls"
6 "fmt"
7 "net/http"
8 "os"
9
10 Lattice "github.com/anduril/lattice-sdk-go/v4"
11 "github.com/anduril/lattice-sdk-go/v4/client"
12 "github.com/anduril/lattice-sdk-go/v4/option"
13)
14
15func main() {
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 clientID := os.Getenv("LATTICE_CLIENT_ID")
18 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
19 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
20
21 // Load the SKIP_TLS_VERIFY variable
22 skipTLSVerify := os.Getenv("SKIP_TLS_VERIFY") == "true"
23
24 // Initialize the client
25 LatticeClient, err := createLatticeClient(latticeEndpoint, clientID, clientSecret,
26 sandboxesToken,
27 skipTLSVerify,
28 )
29 if err != nil {
30 fmt.Printf("Error creating Lattice client: %v\n", err)
31 os.Exit(1)
32 }
33
34 // Use the client
35 ctx := context.Background()
36 entity, err := LatticeClient.Entities.GetEntity(
37 ctx,
38 &Lattice.GetEntityRequest{
39 EntityID: "Demo-Sim-Asset1",
40 },
41 )
42
43 if err != nil {
44 fmt.Printf("Error fetching entity: %v\n", err)
45 } else {
46 fmt.Printf("Asset name | %s\n", *entity.GetAliases().GetName())
47 fmt.Printf("Asset location | %f, %f\n", *entity.GetLocation().GetPosition().GetLatitudeDegrees(),
48 *entity.GetLocation().GetPosition().GetLongitudeDegrees())
49 }
50}
51
52func createLatticeClient(endpoint string, clientID string, clientSecret string,
53 sandboxesToken string,
54 skipTLSVerify bool,
55) (*client.Client, error) {
56
57 headers := http.Header{}
58 if sandboxesToken != "" {
59 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
60 }
61
62 // Configure the InsecureSkipVerify option
63 httpClient := &http.Client{
64 Transport: &http.Transport{
65 TLSClientConfig: &tls.Config{
66 InsecureSkipVerify: skipTLSVerify,
67 },
68 },
69 }
70
71 latticeClient := client.NewClient(
72 option.WithClientCredentials(clientID, clientSecret),
73 option.WithBaseURL(fmt.Sprintf("https://%s", endpoint)),
74 option.WithHTTPHeader(headers),
75 option.WithHTTPClient(httpClient),
76 )
77
78 return latticeClient, nil
79}
2

Create a custom client

Configure a custom client that skips TLS verification. In Go, this uses the InsecureSkipVerify option; the other languages provide an equivalent:

1package main
2
3import (
4 "context"
5 "crypto/tls"
6 "fmt"
7 "net/http"
8 "os"
9
10 Lattice "github.com/anduril/lattice-sdk-go/v4"
11 "github.com/anduril/lattice-sdk-go/v4/client"
12 "github.com/anduril/lattice-sdk-go/v4/option"
13)
14
15func main() {
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 clientID := os.Getenv("LATTICE_CLIENT_ID")
18 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
19 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
20
21 // Load the SKIP_TLS_VERIFY variable
22 skipTLSVerify := os.Getenv("SKIP_TLS_VERIFY") == "true"
23
24 // Initialize the client
25 LatticeClient, err := createLatticeClient(latticeEndpoint, clientID, clientSecret,
26 sandboxesToken,
27 skipTLSVerify,
28 )
29 if err != nil {
30 fmt.Printf("Error creating Lattice client: %v\n", err)
31 os.Exit(1)
32 }
33
34 // Use the client
35 ctx := context.Background()
36 entity, err := LatticeClient.Entities.GetEntity(
37 ctx,
38 &Lattice.GetEntityRequest{
39 EntityID: "Demo-Sim-Asset1",
40 },
41 )
42
43 if err != nil {
44 fmt.Printf("Error fetching entity: %v\n", err)
45 } else {
46 fmt.Printf("Asset name | %s\n", *entity.GetAliases().GetName())
47 fmt.Printf("Asset location | %f, %f\n", *entity.GetLocation().GetPosition().GetLatitudeDegrees(),
48 *entity.GetLocation().GetPosition().GetLongitudeDegrees())
49 }
50}
51
52func createLatticeClient(endpoint string, clientID string, clientSecret string,
53 sandboxesToken string,
54 skipTLSVerify bool,
55) (*client.Client, error) {
56
57 headers := http.Header{}
58 if sandboxesToken != "" {
59 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
60 }
61
62 // Configure the InsecureSkipVerify option
63 httpClient := &http.Client{
64 Transport: &http.Transport{
65 TLSClientConfig: &tls.Config{
66 InsecureSkipVerify: skipTLSVerify,
67 },
68 },
69 }
70
71 latticeClient := client.NewClient(
72 option.WithClientCredentials(clientID, clientSecret),
73 option.WithBaseURL(fmt.Sprintf("https://%s", endpoint)),
74 option.WithHTTPHeader(headers),
75 option.WithHTTPClient(httpClient),
76 )
77
78 return latticeClient, nil
79}
3

Use the client

Use the client to call the Lattice API. This example uses GetEntity
to fetch an entity by its entityId:

1package main
2
3import (
4 "context"
5 "crypto/tls"
6 "fmt"
7 "net/http"
8 "os"
9
10 Lattice "github.com/anduril/lattice-sdk-go/v4"
11 "github.com/anduril/lattice-sdk-go/v4/client"
12 "github.com/anduril/lattice-sdk-go/v4/option"
13)
14
15func main() {
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 clientID := os.Getenv("LATTICE_CLIENT_ID")
18 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
19 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
20
21 // Load the SKIP_TLS_VERIFY variable
22 skipTLSVerify := os.Getenv("SKIP_TLS_VERIFY") == "true"
23
24 // Initialize the client
25 LatticeClient, err := createLatticeClient(latticeEndpoint, clientID, clientSecret,
26 sandboxesToken,
27 skipTLSVerify,
28 )
29 if err != nil {
30 fmt.Printf("Error creating Lattice client: %v\n", err)
31 os.Exit(1)
32 }
33
34 // Use the client
35 ctx := context.Background()
36 entity, err := LatticeClient.Entities.GetEntity(
37 ctx,
38 &Lattice.GetEntityRequest{
39 EntityID: "Demo-Sim-Asset1",
40 },
41 )
42
43 if err != nil {
44 fmt.Printf("Error fetching entity: %v\n", err)
45 } else {
46 fmt.Printf("Asset name | %s\n", *entity.GetAliases().GetName())
47 fmt.Printf("Asset location | %f, %f\n", *entity.GetLocation().GetPosition().GetLatitudeDegrees(),
48 *entity.GetLocation().GetPosition().GetLongitudeDegrees())
49 }
50}
51
52func createLatticeClient(endpoint string, clientID string, clientSecret string,
53 sandboxesToken string,
54 skipTLSVerify bool,
55) (*client.Client, error) {
56
57 headers := http.Header{}
58 if sandboxesToken != "" {
59 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
60 }
61
62 // Configure the InsecureSkipVerify option
63 httpClient := &http.Client{
64 Transport: &http.Transport{
65 TLSClientConfig: &tls.Config{
66 InsecureSkipVerify: skipTLSVerify,
67 },
68 },
69 }
70
71 latticeClient := client.NewClient(
72 option.WithClientCredentials(clientID, clientSecret),
73 option.WithBaseURL(fmt.Sprintf("https://%s", endpoint)),
74 option.WithHTTPHeader(headers),
75 option.WithHTTPClient(httpClient),
76 )
77
78 return latticeClient, nil
79}

What’s next