An open API service indexing awesome lists of open source software.

https://github.com/wiedymi/swift-cloudflared

Pure Swift Cloudflare Access TCP tunnel SDK for SSH clients on Apple platforms (iOS/macOS).
https://github.com/wiedymi/swift-cloudflared

cloudflare cloudflare-access ios libssh2 macos networking ssh swift swift-package-manager

Last synced: 6 days ago
JSON representation

Pure Swift Cloudflare Access TCP tunnel SDK for SSH clients on Apple platforms (iOS/macOS).

Awesome Lists containing this project

README

          

# swift-cloudflared

[![GitHub](https://img.shields.io/badge/-GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/wiedymi)
[![Twitter](https://img.shields.io/badge/-Twitter-1DA1F2?style=flat-square&logo=twitter&logoColor=white)](https://x.com/wiedymi)
[![Email](https://img.shields.io/badge/-Email-EA4335?style=flat-square&logo=gmail&logoColor=white)](mailto:contact@wiedymi.com)
[![Discord](https://img.shields.io/badge/-Discord-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/zemMZtrkSb)
[![Support me](https://img.shields.io/badge/-Support%20me-ff69b4?style=flat-square&logo=githubsponsors&logoColor=white)](https://github.com/sponsors/vivy-company)

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:`) and connect your SSH stack (for example `libssh2`) through it.

## Features

- 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`

## Platforms

- iOS 16+
- macOS 13+
- Swift tools 6.0+

## Installation

Add to your `Package.swift`:

```swift
dependencies: [
.package(url: "https://github.com/wiedymi/swift-cloudflared.git", from: "0.1.0")
]
```

Then add the library target:

```swift
.target(
name: "YourApp",
dependencies: [
.product(name: "Cloudflared", package: "swift-cloudflared")
]
)
```

## Quick Start (Service Token)

```swift
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: "",
clientSecret: ""
)
)

// Use 127.0.0.1:localPort from libssh2
print("Tunnel endpoint: 127.0.0.1:\(localPort)")
```

## OAuth Flow Integration

OAuth UI/token acquisition is app-owned via `OAuthFlow`:

```swift
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`.

## State Stream

Observe state changes from the session:

```swift
Task {
for await state in session.state {
print("state:", state)
}
}
```

States: `idle`, `authenticating`, `connecting`, `connected(localPort)`, `reconnecting(attempt)`, `disconnected`, `failed`.

## libssh2 Integration

Once connected, point your SSH stack to loopback:

```c
// Connect libssh2 socket to 127.0.0.1:
// then run regular libssh2 handshake/auth/channel flow.
```

Example runtime mapping:
- Host: `127.0.0.1`
- Port: ``

## Token Storage Customization

If you need your own keychain layout or storage backend, implement `TokenStore`:

```swift
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:

```swift
// Option 1: explicit sync mode on the base store
let store = KeychainTokenStore(syncMode: .iCloud)

// Option 2: dedicated convenience wrapper
let store = ICloudKeychainTokenStore()
```

## Local Security Defaults

`CloudflareTunnelProvider` defaults to:
- `maxConcurrentConnections = 1`
- `stopAcceptingAfterFirstConnection = true`

You can override via:

```swift
let tunnel = CloudflareTunnelProvider(
connectionLimits: .init(
maxConcurrentConnections: 2,
stopAcceptingAfterFirstConnection: false
)
)
```

## Sandbox and Entitlements

- 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

## E2E Harness

Run the local interactive harness:

```bash
swift run cloudflared-e2e
```

It prints a local endpoint (`127.0.0.1:`) you can test with SSH/libssh2.

## Development

```bash
swift test
swift build
```

Optional keychain integration test:

```bash
CLOUDFLARED_KEYCHAIN_TESTS=1 swift test --filter TokenStoreTests/testKeychainStoreRoundTrip
```

## Docs

- `docs/SPEC.md` - requirements and acceptance criteria
- `docs/ARCHITECTURE.md` - design and concurrency model
- `docs/API.md` - API surface and compatibility notes
- `docs/PROTOCOL_MAPPING.md` - upstream behavior mapping
- `docs/TEST_COVERAGE.md` - test traceability/coverage gates

## Repository Notes

- `reference/cloudflared` is included as a git submodule for upstream reference.

## License

- Root project: MIT (`LICENSE`)
- Third-party notices: `THIRD_PARTY_NOTICES.md`