Pure Swift Cloudflare Access TCP tunnel SDK for SSH clients on Apple platforms.
Use it to open an Access-authenticated local endpoint (127.0.0.1:<port>) and connect your SSH stack (for example libssh2) through it.
- OAuth and Service Token auth method support
- Async session API with connection state stream
- Local loopback tunnel endpoint for SSH client libraries
- Secure default local listener policy:
- one active local client by default
- listener closes after first accepted client by default
- Pluggable auth, token storage, and tunnel layers
- Built-in keychain token store on
macOS,iOS,tvOS,watchOS
- iOS 16+
- macOS 13+
- Swift tools 6.0+
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/wiedymi/swift-cloudflared.git", from: "0.1.0")
]Then add the library target:
.target(
name: "YourApp",
dependencies: [
.product(name: "Cloudflared", package: "swift-cloudflared")
]
)import Cloudflared
let session = SessionActor(
authProvider: ServiceTokenProvider(),
tunnelProvider: CloudflareTunnelProvider(),
retryPolicy: RetryPolicy(maxReconnectAttempts: 2, baseDelayNanoseconds: 500_000_000),
oauthFallback: nil,
sleep: { delay in try? await Task.sleep(nanoseconds: delay) }
)
let localPort = try await session.connect(
hostname: "ssh.example.com",
method: .serviceToken(
teamDomain: "your-team.cloudflareaccess.com",
clientID: "<CF_ACCESS_CLIENT_ID>",
clientSecret: "<CF_ACCESS_CLIENT_SECRET>"
)
)
// Use 127.0.0.1:localPort from libssh2
print("Tunnel endpoint: 127.0.0.1:\(localPort)")OAuth UI/token acquisition is app-owned via OAuthFlow:
import Cloudflared
struct MyOAuthFlow: OAuthFlow {
func fetchToken(
teamDomain: String,
appDomain: String,
callbackScheme: String,
hostname: String
) async throws -> String {
// Implement your Access login UX (for example ASWebAuthenticationSession)
// and return CF_Authorization JWT.
throw Failure.auth("not implemented")
}
}
let oauthProvider = OAuthProvider(
flow: MyOAuthFlow(),
tokenStore: KeychainTokenStore()
)For app metadata discovery (authDomain, appDomain, appAUD) you can use AppInfoResolver.
Observe state changes from the session:
Task {
for await state in session.state {
print("state:", state)
}
}States: idle, authenticating, connecting, connected(localPort), reconnecting(attempt), disconnected, failed.
Once connected, point your SSH stack to loopback:
// Connect libssh2 socket to 127.0.0.1:<localPort>
// then run regular libssh2 handshake/auth/channel flow.Example runtime mapping:
- Host:
127.0.0.1 - Port:
<localPort from session.connect(...)>
If you need your own keychain layout or storage backend, implement TokenStore:
import Cloudflared
actor CustomTokenStore: TokenStore {
func readToken(for key: String) async throws -> String? { nil }
func writeToken(_ token: String, for key: String) async throws {}
func removeToken(for key: String) async throws {}
}Then inject it into OAuthProvider.
KeychainTokenStore defaults:
- iOS/tvOS/watchOS:
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly - macOS:
kSecAttrAccessibleAfterFirstUnlock+ data-protection keychain mode
iCloud Keychain options:
// Option 1: explicit sync mode on the base store
let store = KeychainTokenStore(syncMode: .iCloud)
// Option 2: dedicated convenience wrapper
let store = ICloudKeychainTokenStore()CloudflareTunnelProvider defaults to:
maxConcurrentConnections = 1stopAcceptingAfterFirstConnection = true
You can override via:
let tunnel = CloudflareTunnelProvider(
connectionLimits: .init(
maxConcurrentConnections: 2,
stopAcceptingAfterFirstConnection: false
)
)- iOS app sandbox: supported (foreground usage; OAuth flow is app-defined).
- macOS App Sandbox: enable network client/server entitlements if sandboxed, because the SDK opens:
- outbound websocket connection to Cloudflare Access
- local loopback listener for SSH client connection
Run the local interactive harness:
swift run cloudflared-e2eIt prints a local endpoint (127.0.0.1:<port>) you can test with SSH/libssh2.
swift test
swift buildOptional keychain integration test:
CLOUDFLARED_KEYCHAIN_TESTS=1 swift test --filter TokenStoreTests/testKeychainStoreRoundTripdocs/SPEC.md- requirements and acceptance criteriadocs/ARCHITECTURE.md- design and concurrency modeldocs/API.md- API surface and compatibility notesdocs/PROTOCOL_MAPPING.md- upstream behavior mappingdocs/TEST_COVERAGE.md- test traceability/coverage gates
reference/cloudflaredis included as a git submodule for upstream reference.
- Root project: MIT (
LICENSE) - Third-party notices:
THIRD_PARTY_NOTICES.md