Awesome
Ziti SDK for Swift
An SDK for accessing Ziti from macOS and iOS applications using the Swift programming language.
This SDK provides a Swift-friendly wrapper of the Ziti Tunnel C SDK, an implementation of URLProtocol
for intercepting HTTP and HTTPS traffic, and examples of using the SDK in an application.
Usage
The Ziti
class is the main entry point for accessing Ziti networks. An instance of Ziti
requires a ZitiIdentity
at time of initialization.
Use Ziti.createConnection()
to create instances of ZitiConnection
to ZitiConnection.dial(_:_:_:)
services or ZitiConnection.listen(_:_:_:)
for service connections.
Use ZitiUrlProtocol
to intercept HTTP and HTTPS connections and route them over a Ziti network.
Enrollment
A ZitiIdentity
is created as part of the enrollment process with a Ziti network. Ziti
support enrollment using a one-time JWT supplied by your Ziti network administror.
The Ziti.enroll(_:_:)
method validates the JWT is properly signed, creates a private key and stores it in the keychain, initiates a Certificate Signing Request (CSR) with the controller, and stores the resultant certificate in the keychain.
Swift
import CZiti
let jwtFile = <...>
let outFile = <...>
Ziti.enroll(jwtFile) { zid, zErr in
guard let zid = zid else {
fputs("Invalid enrollment response, \(String(describing: zErr))\n", stderr)
exit(-1)
}
guard zid.save(outFile) else {
fputs("Unable to save to file \(outFile)\n", stderr)
exit(-1)
}
print("Successfully enrolled id \"\(zid.id)\" with controller \"\(zid.ztAPI)\"")
}
Objective-C
#import "CZiti-Swift.h"
NSString *jwtFile = <...>
NSString *outFile = <...>
[Ziti enroll:jwtFile : ^(ZitiIdentity *zid, ZitiError *zErr) {
if (zErr != NULL) {
// Handle error
return;
}
if (![zid save:outFile]) {
// Handle error
return;
}
}];
The identity file saved to outfile
in the example code above contains information for contacting the Ziti controller and locally accessing the private key and certificate in the keychain.
Running Ziti
A typical application flow would:
- Check a well-known location for a stored identity file
- If not present, initiate an enrollment (e.g., prompt the user for location of a one-time JWT enrollment file, or scan in a QR code)
- When identity file is available, use it to create and run an instance of
Ziti
Ziti
executes on a loop, similar to Foundation
's Runloop
. The Ziti.run(_:)
method essentially enters an infinite loop processing Ziti events, and will only exit after Ziti
is shut down.
The Ziti.runAsync(_:)
method is provided as a convenience to spawn a new thread and call Ziti.run(_:)
.
Swift
let zidFile = <...>
guard let ziti = Ziti(fromFile: zidFile) else {
print("Unable to create Ziti identity from file \(zidFile)")
return
}
ziti.runAsync { zErr in
guard zErr == nil else {
print("Unable to run Ziti: \(String(describing: zErr!))")
return
}
print("Successfully initialized Ziti!")
}
Objective-C
NSString *zidFile = <...>
Ziti *ziti = [[Ziti alloc] initFromFile:[self zidFile]];
if (ziti != NULL) {
[ziti runAsync: ^(ZitiError *zErr) {
if (zErr != NULL) {
// Handle error
return;
}
[ZitiUrlProtocol register:ziti :10000];
}];
}
To execute code on the thread running Ziti use the perform(_:)
method.
Using ZitiUrlProtocol
The SDK also includes ZitiUrlProtocol
, which implements a URLProtocol
that interceptes HTTP and HTTPS requests for Ziti services and routes them over a Ziti network.
ZitiUrlProtocol
should be instantiated as part of the Ziti.InitCallback
of Ziti.run(_:)
to ensure Ziti
is initialized before
starting to intercept services.
Swift
ziti.runAsync { zErr in
guard zErr == nil else {
// Handle error
return
}
ZitiUrlProtocol.register(ziti)
}
Objective-C
[ziti runAsync: ^(ZitiError *zErr) {
if (zErr != NULL) {
// Handle error
return;
}
[ZitiUrlProtocol register:ziti :10000];
}];
If using your own URLSession
insteal of URLSession.shared
, ZitiUrlProtocol
will need to be configured in your URLSession
's configuration:
let configuration = URLSessionConfiguration.default
configuration.protocolClasses?.insert(ZitiUrlProtocol.self, at: 0)
urlSession = URLSession(configuration:configuration)
See also the documentation included in the CZiti
module available in the Xcode
Quick Help pane.
Adding CZiti
as a Dependency
CZiti
is built into an XCFramework (CZiti.xcframework
) that includes a static library (libCZiti.a
) for each platform and architecture.
Note that that CZiti
is not built for Bitcode, and when building for a device the Build Settings - Build Options should set Enable Bitcode
to No
.
Note that CZiti
depends on libresolv.9.tbd
, and requires access to outbound network connections and the Apple Keychain.
Via Swift Package Manager
See ziti-sdk-swift-dist for access to CZiti.xcframework
built from this repository and made available as a .binaryTarget
.
Using a locally built CZiti
To exercise modifications to the CZiti framework in your application, first add the CZiti module as a dependency to your app as described above, then override the CZiti framework with a local package. See Editing a package dependency as a local package for an overview of this process.
I used the following steps to override CZiti with a local build in the ziti-tunnel-apple project:
-
Build the CZiti framework. You may want to build for debugging:
$ CONFIGURATION=Debug ./build_all.sh
When complete, the build will be located in ./dist/CZiti.xcframework.
-
Create a Package.swift for the local CZiti framework:
cat > ./dist/Package.swift <<EOF // swift-tools-version: 5.7 import PackageDescription let package = Package( name: "CZiti", platforms: [ .macOS(.v10_14), .iOS(.v13) ], products: [ .library( name: "CZiti", targets: ["CZiti"]) ], targets: [ .binaryTarget( name: "CZiti", path: "./CZiti.xcframework") ] ) EOF
-
Move or copy the ./dist directory into your application's top-level source directory, renaming it to 'ziti-sdk-swift-dist'. The final directory structure should look like this:
- YourApplication/ - ziti-sdk-swift-dist/ - Package.swift - CZiti.xcframework/
The apple documentation doesn't mention this, but I was unable to get Xcode to recognize that the local package overrides the released CZiti framework unless the parent directory of the local CZiti framework was named 'ziti-sdk-swift-dist' (to match the github repo the CZiti releases come from), and local CZiti framework was in the application's directory.
-
Add the local package to your application. This can be done by clicking "Add Local..." while adding a package dependency to your application. Select the 'ziti-sdk-swift-dist` directory in the dialog.
You should see the CZiti entry disappear from your project's Package Dependencies in the project navigator when the local CZiti package is referenced.
Via CocoaPods
DEPECATED AS OF v0.30.11, please convert to use of Swift Package Manager
If you are using Cocoapods, update your Podfile
:
target 'Some-macOS-Target'
use_frameworks!
platform :macos, '10.15'
pod 'CZiti-macOS', '~> 0.1'
end
target 'SomeTarget-iOS-Target'
use_frameworks!
platform :ios, '13.4'
pod 'CZiti-iOS', '~> 0.1'
end
For further information on Cocoapods, check their official documentation.
Via libCZiti.a
- Follow the build steps below to create
libCZiti.a
- Add
libCZiti.a
library to your project's Frameworks and Libraries, and ensure it is listed in your project's Build Phases under Link Binary with Libraries. - Your Library Search Path and Swift Compiler Seatch Paths - Import Paths should include the directory containing
libCZiti.a
andCZiti.swiftmodule/
- When this project is built from
Xcode
, theCZiti-Swift.h
file is copied to$(PROJECT_ROOT)/include/$(PLATFORM)
(e.g.,./include/iphoneos
).CZiti-Swift.h
can also be found the theDerivedSources
directory under./DerivedData
following a build from eitherXcode
or viabuild_all.sh
. This file is needed to useCZiti
from Objective-C. Your Search Paths - Header Search Paths must include the directory containingCZiti-Swift.h
. - Inspect the sample apps' configurations in this repository for relevant build settings for libraries and paths
Examples
This repository includes a few examples of using the library:
ziti-mac-enroller
is a utility that will enroll an identity using a supplied one-time JWT token. It can optionally update the keychain to trust for the CA pool used by the Ziti controllersample-mac-host
is a command-line utility that can operate as either a client or a server for a specified Ziti serversample-ios
exercisesZitiUrlProtocol
to interceptURLSesson
requests, route them over Ziti, and display the resultssample-ios-objc
demonstrates using Objective-C to exerciseZitiUrlProtocol
Building
Update xcconfig
Settings
Create a file called Configs/workspace-settings-overrides.xcconfig
and populate with appropriate values. See Configs/workplace-settings.xcconfig
for all possible values.
DEVELOPMENT_TEAM = XXXXXXXXXX
ORGANIZATION_PREFIX = ...
Execute Build Script
The project depends on the Ziti Tunnel C SDK, which is built directly into the library. It is maintained as a submodule at ./deps/ziti-tunnel-sdk-c
. Be sure to follow the vcpkg setup steps in deps/ziti-tunnel-sdk-c/BUILD.md. This project expects builds to be built in ./deps/ziti-tunnel-sdk-c/build-macosx-x86_64
and ./deps/ziti-tunnel-sdk-c/build-macosx-arm64
for macOS and ./deps/ziti-sdk-c/build-iphoneos-arm64
for iOS (or build-iphonesimulator-x86_64
or build-iphonesimulator-arm64
for the simulator).
This project contains the buid_all.sh
script that will build the project from the command-line for macosx
, iphoneos
, and iphonesimulator
platforms.
Once the static libraries are built, the build_all.sh
script executes make_dist.sh
, creating an XCFramework called CZiti.xcframework
, under the project's ./dist
directory.
The scripts require the following executables to be on the caller's path:
xcodebuild
used to buildCZiti-*
schemes inCZiti.xcodeproj
, avaialble as part of yourXcode
installationxcpretty
also used to buildCZiti-*
schemes inCZiti.xcodeproj
. (Can be installed viagem install xcpretty
)cmake
used for building the Ziti Tunnel C SDK dependency. (Can be installed viabrew install cmake
)
$ git clone --recurse-submodules https://github.com/openziti/ziti-sdk-swift.git
$ cd ziti-sdk-swift
$ /bin/sh build_all.sh
By default, the scripts build for Release
configuration. To build for Debug
, execute
$ CONFIGURATION=Debug /bin/sh build_all.sh
The resultant libCZiti.a
and CZiti.swiftmodule
are available in the appropriate sub-directory of ./DerivedData
.
Getting Help
Please use these community resources for getting help.
- Read the docs
- Participate in discussion on Discourse
- Use GitHub issues for tracking bugs and feature requests.
Copyright NetFoundry Inc.