mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Allow data plane configuration at runtime (#2477)
## Changelog - Updates connlib parameter API_URL (formerly known under different names as `CONTROL_PLANE_URL`, `PORTAL_URL`, `PORTAL_WS_URL`, and friends) to be configured as an "advanced" or "hidden" feature at runtime so that we can test production builds on both staging and production. - Makes `AUTH_BASE_URL` configurable at runtime too - Moves `CONNLIB_LOG_FILTER_STRING` to be configured like this as well and simplifies its naming - Fixes a timing attack bug on Android when comparing the `csrf` token - Adds proper account ID validation to Android to prevent invalid URL parameter strings from being saved and used - Cleans up a number of UI / view issues on Android regarding typos, consistency, etc - Hides vars from from the `relay` CLI we may not want to expose just yet - `get_device_id()` is flawed for connlib components -- SMBios is rarely available. Data plane components now require a `FIREZONE_ID` now instead to use for upserting. Fixes #2482 Fixes #2471 --------- Signed-off-by: Jamil <jamilbk@users.noreply.github.com> Co-authored-by: Gabi <gabrielalejandro7@gmail.com>
This commit is contained in:
4
.github/workflows/_swift.yml
vendored
4
.github/workflows/_swift.yml
vendored
@@ -117,14 +117,12 @@ jobs:
|
||||
# Needed because `productbuild` doesn't support picking this up automatically like Xcode does
|
||||
INSTALLER_CODE_SIGN_IDENTITY: "3rd Party Mac Developer Installer: Firezone, Inc. (47R2M6779T)"
|
||||
REQUESTED_XCODE_VERSION: ${{ matrix.xcode }}
|
||||
# TODO: Change this to "prod" when app is released
|
||||
XCCONFIG_ENV: staging
|
||||
run: |
|
||||
# Set Xcode version to use if provided
|
||||
[[ ! -z "$REQUESTED_XCODE_VERSION" ]] && sudo xcode-select -s /Applications/Xcode_$REQUESTED_XCODE_VERSION.app
|
||||
|
||||
# Copy xcconfig
|
||||
cp Firezone/xcconfig/${{ env.XCCONFIG_ENV }}.xcconfig Firezone/xcconfig/config.xcconfig
|
||||
cp Firezone/xcconfig/release.xcconfig Firezone/xcconfig/config.xcconfig
|
||||
|
||||
# App Store Connect requires a new build version on each upload and it must be an integer.
|
||||
# See https://developer.apple.com/documentation/xcode/build-settings-reference#Current-Project-Version
|
||||
|
||||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -128,7 +128,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# TODO: Add more NAT type tests here
|
||||
# TODO
|
||||
# - Run control plane components as services
|
||||
# - Test clients
|
||||
# - Test with different NAT types
|
||||
# - Test IPv6
|
||||
# - Test end-to-end critical paths
|
||||
- test_name: Relayed flow
|
||||
setup: |
|
||||
# Disallow traffic between gateway and client container
|
||||
|
||||
@@ -113,9 +113,10 @@ services:
|
||||
|
||||
client:
|
||||
environment:
|
||||
PORTAL_URL: "ws://api:8081/"
|
||||
PORTAL_TOKEN: "SFMyNTY.g2gDaAN3CGlkZW50aXR5bQAAACQ3ZGE3ZDFjZC0xMTFjLTQ0YTctYjVhYy00MDI3YjlkMjMwZTVtAAAAIBn8Xu1jtFlxZxp4ZvAz0f0QEN2PZThA-7awHMPxn_tHbgYAbLRvQokBYgHhM38.pM-prhb7uvvCVKf51-tAUMEtMzLPZk1n3nLsY44dGFA"
|
||||
FIREZONE_TOKEN: "SFMyNTY.g2gDaAN3CGlkZW50aXR5bQAAACQ3ZGE3ZDFjZC0xMTFjLTQ0YTctYjVhYy00MDI3YjlkMjMwZTVtAAAAIBn8Xu1jtFlxZxp4ZvAz0f0QEN2PZThA-7awHMPxn_tHbgYAbLRvQokBYgHhM38.pM-prhb7uvvCVKf51-tAUMEtMzLPZk1n3nLsY44dGFA"
|
||||
RUST_LOG: firezone_linux_client=trace,connlib_client_shared=trace,firezone_tunnel=trace,connlib_shared=trace,warn
|
||||
FIREZONE_API_URL: ws://api:8081
|
||||
FIREZONE_ID: D0455FDE-8F65-4960-A778-B934E4E85A5F
|
||||
build:
|
||||
target: debug
|
||||
context: rust
|
||||
@@ -149,10 +150,11 @@ services:
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "ip link | grep tun-firezone"]
|
||||
environment:
|
||||
PORTAL_URL: "ws://api:8081/"
|
||||
PORTAL_TOKEN: "SFMyNTY.g2gDaAJtAAAAJDNjZWYwNTY2LWFkZmQtNDhmZS1hMGYxLTU4MDY3OTYwOGY2Zm0AAABAamp0enhSRkpQWkdCYy1vQ1o5RHkyRndqd2FIWE1BVWRwenVScjJzUnJvcHg3NS16bmhfeHBfNWJUNU9uby1yYm4GAEC0b0KJAWIAAVGA.9Oirn9t8rvQpfOhW7hwGBFVzeMm9di0xYGTlwf9cFFk"
|
||||
FIREZONE_TOKEN: "SFMyNTY.g2gDaAJtAAAAJDNjZWYwNTY2LWFkZmQtNDhmZS1hMGYxLTU4MDY3OTYwOGY2Zm0AAABAamp0enhSRkpQWkdCYy1vQ1o5RHkyRndqd2FIWE1BVWRwenVScjJzUnJvcHg3NS16bmhfeHBfNWJUNU9uby1yYm4GAEC0b0KJAWIAAVGA.9Oirn9t8rvQpfOhW7hwGBFVzeMm9di0xYGTlwf9cFFk"
|
||||
RUST_LOG: firezone_gateway=trace,connlib_gateway_shared=trace,firezone_tunnel=trace,connlib_shared=trace,warn
|
||||
FIREZONE_ENABLE_MASQUERADE: 1
|
||||
FIREZONE_API_URL: ws://api:8081
|
||||
FIREZONE_ID: 4694E56C-7643-4A15-9DF3-638E5B05F570
|
||||
build:
|
||||
target: debug
|
||||
context: rust
|
||||
@@ -201,10 +203,10 @@ services:
|
||||
PUBLIC_IP6_ADDR: fcff:3990:3990::101
|
||||
LOWEST_PORT: 55555
|
||||
HIGHEST_PORT: 55666
|
||||
PORTAL_URL: "ws://api:8081/"
|
||||
PORTAL_TOKEN: "SFMyNTY.g2gDaAJtAAAAJDcyODZiNTNkLTA3M2UtNGM0MS05ZmYxLWNjODQ1MWRhZDI5OW0AAABARVg3N0dhMEhLSlVWTGdjcE1yTjZIYXRkR25mdkFEWVFyUmpVV1d5VHFxdDdCYVVkRVUzbzktRmJCbFJkSU5JS24GAFSzb0KJAWIAAVGA.waeGE26tbgkgIcMrWyck0ysv9SHIoHr0zqoM3wao84M"
|
||||
FIREZONE_TOKEN: "SFMyNTY.g2gDaAJtAAAAJDcyODZiNTNkLTA3M2UtNGM0MS05ZmYxLWNjODQ1MWRhZDI5OW0AAABARVg3N0dhMEhLSlVWTGdjcE1yTjZIYXRkR25mdkFEWVFyUmpVV1d5VHFxdDdCYVVkRVUzbzktRmJCbFJkSU5JS24GAFSzb0KJAWIAAVGA.waeGE26tbgkgIcMrWyck0ysv9SHIoHr0zqoM3wao84M"
|
||||
RUST_LOG: "debug"
|
||||
RUST_BACKTRACE: 1
|
||||
FIREZONE_API_URL: ws://api:8081
|
||||
build:
|
||||
target: debug
|
||||
context: rust
|
||||
|
||||
@@ -98,7 +98,7 @@ defmodule Web.RelayGroups.New do
|
||||
--name=firezone-relay-0 \\
|
||||
--restart=always \\
|
||||
-v /dev/net/tun:/dev/net/tun \\
|
||||
-e PORTAL_TOKEN=#{secret} \\
|
||||
-e FIREZONE_TOKEN=#{secret} \\
|
||||
us-east1-docker.pkg.dev/firezone/firezone/relay:stable
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -119,11 +119,11 @@ defmodule Web.Live.RelayGroups.NewTest do
|
||||
|> render_submit()
|
||||
|
||||
assert html =~ "Select deployment method"
|
||||
assert html =~ "PORTAL_TOKEN="
|
||||
assert html =~ "FIREZONE_TOKEN="
|
||||
assert html =~ "docker run"
|
||||
assert html =~ "Waiting for relay connection..."
|
||||
|
||||
token = Regex.run(~r/PORTAL_TOKEN=([^ ]+)/, html) |> List.last()
|
||||
token = Regex.run(~r/FIREZONE_TOKEN=([^ ]+)/, html) |> List.last()
|
||||
assert {:ok, _token} = Domain.Relays.authorize_relay(token)
|
||||
|
||||
group = Repo.get_by(Domain.Relays.Group, name: attrs.name) |> Repo.preload(:tokens)
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
# Firezone Android client
|
||||
|
||||
## Prerequisites for developing locally
|
||||
This README contains instructions for building and testing the Android client
|
||||
locally.
|
||||
|
||||
1. Install a recent `ruby` for your platform. Ruby is used for the mock auth
|
||||
server.
|
||||
1. Install needed gems and start mock auth server:
|
||||
## Dev Setup
|
||||
|
||||
1. [Install Rust](https://www.rust-lang.org/tools/install)
|
||||
1. [Install Android Studio](https://developer.android.com/studio)
|
||||
1. Install your JDK 17 of choice. We recommend just
|
||||
[updating your CLI](https://stackoverflow.com/questions/43211282/using-jdk-that-is-bundled-inside-android-studio-as-java-home-on-mac)
|
||||
environment to use the JDK bundled in Android Studio to ensure you're using
|
||||
the same JDK on the CLI as Android Studio.
|
||||
1. Perform a test build: `./gradlew assembleDebug`
|
||||
1. Add your debug signing key's SHA256 fingerprint to the portal's
|
||||
[`assetlinks.json`](../../elixir/apps/web/priv/static/.well-known/assetlinks.json)
|
||||
file. This is required for the App Links to successfully intercept the Auth
|
||||
redirect.
|
||||
```
|
||||
./gradlew signingReport
|
||||
```
|
||||
|
||||
# Release Setup
|
||||
|
||||
We release from GitHub CI, so this shouldn't be necessary. But if you're looking
|
||||
to test the `release` variant locally:
|
||||
|
||||
1. Download the keystore from 1Pass and save to `app/.signing/keystore.jks` dir.
|
||||
1. Download firebase credentials from 1Pass and save to
|
||||
`app/.signing/firebase.json`
|
||||
1. Now you can execute the `*Release` tasks with:
|
||||
|
||||
```shell
|
||||
export KEYSTORE_PATH="$(pwd)/app/.signing/keystore.jks"
|
||||
export FIREBASE_CREDENTIALS_PATH="$(pwd)/app/.signing/firebase.json"
|
||||
HISTCONTROL=ignorespace # prevents saving the next line in shell history
|
||||
KEYSTORE_PASSWORD='keystore_password' KEYSTORE_KEY_PASSWORD='keystore_key_password' ./gradlew assembleRelease
|
||||
```
|
||||
cd server
|
||||
bundle install
|
||||
ruby server.rb
|
||||
```
|
||||
|
||||
1. Add the following to a `./local.properties` file:
|
||||
|
||||
```gradle
|
||||
sdk.dir=/path/to/your/ANDROID_HOME
|
||||
```
|
||||
|
||||
Replace `/path/to/your/ANDROID_HOME` with the path to your locally installed
|
||||
Android SDK. On macOS this is `/Users/jamil/Library./Android/sdk`
|
||||
|
||||
1. Perform a test build: `./gradlew build`
|
||||
|
||||
@@ -65,20 +65,15 @@ android {
|
||||
// Debug Config
|
||||
getByName("debug") {
|
||||
isDebuggable = true
|
||||
resValue("string", "app_name", "\"Firezone (Dev)\"")
|
||||
|
||||
buildConfigField("String", "AUTH_HOST", "\"app.firez.one\"")
|
||||
buildConfigField("String", "AUTH_SCHEME", "\"https\"")
|
||||
buildConfigField("Integer", "AUTH_PORT", "443")
|
||||
buildConfigField("String", "CONTROL_PLANE_URL", "\"wss://api.firez.one/\"")
|
||||
|
||||
// Docs on filter strings: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html
|
||||
buildConfigField("String", "AUTH_BASE_URL", "\"https://app.firez.one\"")
|
||||
buildConfigField("String", "API_URL", "\"wss://api.firez.one\"")
|
||||
buildConfigField(
|
||||
"String",
|
||||
"CONNLIB_LOG_FILTER_STRING",
|
||||
"LOG_FILTER",
|
||||
"\"connlib_client_android=debug,firezone_tunnel=trace,connlib_shared=debug,connlib_client_shared=debug,warn\"",
|
||||
)
|
||||
|
||||
resValue("string", "app_name", "\"Firezone (Dev)\"")
|
||||
}
|
||||
|
||||
// Release Config
|
||||
@@ -103,25 +98,20 @@ android {
|
||||
)
|
||||
isDebuggable = false
|
||||
|
||||
buildConfigField("String", "AUTH_HOST", "\"app.firezone.dev\"")
|
||||
buildConfigField("String", "AUTH_SCHEME", "\"https\"")
|
||||
buildConfigField("Integer", "AUTH_PORT", "443")
|
||||
buildConfigField("String", "CONTROL_PLANE_URL", "\"wss://api.firezone.dev/\"")
|
||||
|
||||
// Docs on filter strings: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html
|
||||
buildConfigField(
|
||||
"String",
|
||||
"CONNLIB_LOG_FILTER_STRING",
|
||||
"\"connlib_client_android=info,firezone_tunnel=info,connlib_shared=info,connlib_client_shared=info,warn\"",
|
||||
)
|
||||
|
||||
resValue("string", "app_name", "\"Firezone\"")
|
||||
|
||||
buildConfigField("String", "AUTH_BASE_URL", "\"https://app.firezone.dev\"")
|
||||
buildConfigField("String", "API_URL", "\"wss://api.firezone.dev\"")
|
||||
buildConfigField(
|
||||
"String",
|
||||
"LOG_FILTER",
|
||||
"\"connlib_client_android=info,firezone_tunnel=info,connlib_shared=info,connlib_client_shared=info,warn\"",
|
||||
)
|
||||
firebaseAppDistribution {
|
||||
serviceCredentialsFile = System.getenv("FIREBASE_CREDENTIALS_PATH")
|
||||
artifactType = "AAB"
|
||||
releaseNotes = "https://github.com/firezone/firezone/releases"
|
||||
groups = "firezone-engineering"
|
||||
groups = "firezone-engineering, firezone-go-to-market"
|
||||
artifactPath = "app/build/outputs/bundle/release/app-release.aab"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop">
|
||||
|
||||
<!-- Staging -->
|
||||
<intent-filter
|
||||
android:label="@string/app_name"
|
||||
android:autoVerify="true">
|
||||
@@ -66,6 +67,24 @@
|
||||
<data android:host="app.firez.one" />
|
||||
<data android:pathPrefix="/handle_client_auth_callback" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Prod -->
|
||||
<intent-filter
|
||||
android:label="@string/app_name"
|
||||
android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<!-- These must match the resulting URL from the Portal exactly.
|
||||
Don't use variables here otherwise this can break when testing in the emulator.
|
||||
For this to work in the emulator, you must use a host/IP that *both* the emulator and the
|
||||
host can access. E.g. 10.0.2.2 will not work. -->
|
||||
<data android:scheme="https" />
|
||||
<data android:host="app.firezone.dev" />
|
||||
<data android:pathPrefix="/handle_client_auth_callback" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
||||
@@ -3,6 +3,7 @@ package dev.firezone.android.core
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import dev.firezone.android.BuildConfig
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
@@ -14,16 +15,15 @@ internal class BaseUrlInterceptor(
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
val accountId = sharedPreferences.getString(ACCOUNT_ID_KEY, "") ?: ""
|
||||
val newUrl =
|
||||
originalRequest.url.newBuilder()
|
||||
.scheme(BuildConfig.AUTH_SCHEME)
|
||||
.host(BuildConfig.AUTH_HOST)
|
||||
.port(BuildConfig.AUTH_PORT)
|
||||
.addPathSegment(accountId)
|
||||
.build()
|
||||
val newUrl = "${BuildConfig.AUTH_BASE_URL}/$accountId"?.toHttpUrlOrNull()
|
||||
|
||||
if (newUrl == null) {
|
||||
throw IllegalStateException("Invalid AUTH_BASE_URL. Check Settings?")
|
||||
}
|
||||
|
||||
val newRequest =
|
||||
originalRequest.newBuilder()
|
||||
.url(newUrl)
|
||||
.url(newUrl!!)
|
||||
.build()
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,12 @@ internal interface PreferenceRepository {
|
||||
|
||||
fun getConfig(): Flow<Config>
|
||||
|
||||
fun saveAccountId(value: String): Flow<Unit>
|
||||
fun saveSettings(
|
||||
accountId: String,
|
||||
authBaseUrl: String,
|
||||
apiUrl: String,
|
||||
logFilter: String,
|
||||
): Flow<Unit>
|
||||
|
||||
fun saveToken(value: String): Flow<Unit>
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
package dev.firezone.android.core.data
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import dev.firezone.android.BuildConfig
|
||||
import dev.firezone.android.core.data.model.Config
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import java.security.MessageDigest
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class PreferenceRepositoryImpl
|
||||
@@ -18,6 +20,9 @@ internal class PreferenceRepositoryImpl
|
||||
override fun getConfigSync(): Config =
|
||||
Config(
|
||||
accountId = sharedPreferences.getString(ACCOUNT_ID_KEY, null),
|
||||
authBaseUrl = sharedPreferences.getString(AUTH_BASE_URL_KEY, null) ?: BuildConfig.AUTH_BASE_URL,
|
||||
apiUrl = sharedPreferences.getString(API_URL_KEY, null) ?: BuildConfig.API_URL,
|
||||
logFilter = sharedPreferences.getString(LOG_FILTER_KEY, null) ?: BuildConfig.LOG_FILTER,
|
||||
token = sharedPreferences.getString(TOKEN_KEY, null),
|
||||
)
|
||||
|
||||
@@ -26,12 +31,20 @@ internal class PreferenceRepositoryImpl
|
||||
emit(getConfigSync())
|
||||
}.flowOn(coroutineDispatcher)
|
||||
|
||||
override fun saveAccountId(value: String): Flow<Unit> =
|
||||
override fun saveSettings(
|
||||
accountId: String,
|
||||
authBaseUrl: String,
|
||||
apiUrl: String,
|
||||
logFilter: String,
|
||||
): Flow<Unit> =
|
||||
flow {
|
||||
emit(
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putString(ACCOUNT_ID_KEY, value)
|
||||
.putString(ACCOUNT_ID_KEY, accountId)
|
||||
.putString(AUTH_BASE_URL_KEY, authBaseUrl)
|
||||
.putString(API_URL_KEY, apiUrl)
|
||||
.putString(LOG_FILTER_KEY, logFilter)
|
||||
.apply(),
|
||||
)
|
||||
}.flowOn(coroutineDispatcher)
|
||||
@@ -48,8 +61,8 @@ internal class PreferenceRepositoryImpl
|
||||
|
||||
override fun validateCsrfToken(value: String): Flow<Boolean> =
|
||||
flow {
|
||||
val token = sharedPreferences.getString(CSRF_KEY, "")
|
||||
emit(token == value)
|
||||
val token = sharedPreferences.getString(CSRF_KEY, "").orEmpty()
|
||||
emit(MessageDigest.isEqual(token.toByteArray(), value.toByteArray()))
|
||||
}.flowOn(coroutineDispatcher)
|
||||
|
||||
override fun clearToken() {
|
||||
@@ -66,6 +79,9 @@ internal class PreferenceRepositoryImpl
|
||||
|
||||
companion object {
|
||||
private const val ACCOUNT_ID_KEY = "accountId"
|
||||
private const val AUTH_BASE_URL_KEY = "authBaseUrl"
|
||||
private const val API_URL_KEY = "apiUrl"
|
||||
private const val LOG_FILTER_KEY = "logFilter"
|
||||
private const val TOKEN_KEY = "token"
|
||||
private const val CSRF_KEY = "csrf"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package dev.firezone.android.core.data.model
|
||||
|
||||
internal data class Config(
|
||||
val accountId: String?,
|
||||
val isConnected: Boolean = false,
|
||||
val authBaseUrl: String,
|
||||
val apiUrl: String,
|
||||
val logFilter: String,
|
||||
val token: String?,
|
||||
)
|
||||
|
||||
@@ -5,10 +5,15 @@ import dev.firezone.android.core.data.PreferenceRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SaveAccountIdUseCase
|
||||
internal class SaveSettingsUseCase
|
||||
@Inject
|
||||
constructor(
|
||||
private val repository: PreferenceRepository,
|
||||
) {
|
||||
operator fun invoke(accountId: String): Flow<Unit> = repository.saveAccountId(accountId)
|
||||
operator fun invoke(
|
||||
accountId: String,
|
||||
authBaseUrl: String,
|
||||
apiUrl: String,
|
||||
logFilter: String,
|
||||
): Flow<Unit> = repository.saveSettings(accountId, authBaseUrl, apiUrl, logFilter)
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.firezone.android.BuildConfig
|
||||
import dev.firezone.android.core.domain.auth.GetCsrfTokenUseCase
|
||||
import dev.firezone.android.core.domain.preference.GetConfigUseCase
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
@@ -43,7 +42,9 @@ internal class AuthViewModel
|
||||
} else {
|
||||
authFlowLaunched = true
|
||||
ViewAction.LaunchAuthFlow(
|
||||
url = "$AUTH_URL${config.accountId}?client_csrf_token=$csrfToken&client_platform=android",
|
||||
url =
|
||||
"${config.authBaseUrl}/${config.accountId}" +
|
||||
"?client_csrf_token=$csrfToken&client_platform=android",
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -52,10 +53,6 @@ internal class AuthViewModel
|
||||
actionMutableLiveData.postValue(ViewAction.ShowError)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val AUTH_URL = "${BuildConfig.AUTH_SCHEME}://${BuildConfig.AUTH_HOST}:${BuildConfig.AUTH_PORT}/"
|
||||
}
|
||||
|
||||
internal sealed class ViewAction {
|
||||
data class LaunchAuthFlow(val url: String) : ViewAction()
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ internal class SessionActivity : AppCompatActivity() {
|
||||
this@SessionActivity,
|
||||
layoutManager.orientation,
|
||||
)
|
||||
binding.resourcesList.addItemDecoration(dividerItemDecoration)
|
||||
binding.resourcesList.adapter = resourcesAdapter
|
||||
binding.resourcesList.layoutManager = layoutManager
|
||||
binding.rvResourcesList.addItemDecoration(dividerItemDecoration)
|
||||
binding.rvResourcesList.adapter = resourcesAdapter
|
||||
binding.rvResourcesList.layoutManager = layoutManager
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package dev.firezone.android.features.settings.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
@@ -27,15 +28,16 @@ internal class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
||||
setupViews()
|
||||
setupStateObservers()
|
||||
setupActionObservers()
|
||||
setupButtonListener()
|
||||
setupButtonListeners()
|
||||
|
||||
viewModel.getAccountId()
|
||||
viewModel.populateFieldsFromConfig()
|
||||
}
|
||||
|
||||
private fun setupStateObservers() {
|
||||
viewModel.stateLiveData.observe(viewLifecycleOwner) { state ->
|
||||
with(binding) {
|
||||
btLogin.isEnabled = state.isButtonEnabled
|
||||
Log.d("SettingsFragment", "state: $state")
|
||||
btSaveSettings.isEnabled = state.isSaveButtonEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,10 +49,19 @@ internal class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
||||
findNavController().navigate(
|
||||
R.id.signInFragment,
|
||||
)
|
||||
is SettingsViewModel.ViewAction.FillAccountId -> {
|
||||
binding.etInput.apply {
|
||||
setText(action.value)
|
||||
isCursorVisible = false
|
||||
is SettingsViewModel.ViewAction.FillSettings -> {
|
||||
Log.d("SettingsFragment", "action: $action")
|
||||
binding.etAccountIdInput.apply {
|
||||
setText(action.accountId)
|
||||
}
|
||||
binding.etAuthBaseUrlInput.apply {
|
||||
setText(action.authBaseUrl)
|
||||
}
|
||||
binding.etApiUrlInput.apply {
|
||||
setText(action.apiUrl)
|
||||
}
|
||||
binding.etLogFilterInput.apply {
|
||||
setText(action.logFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,24 +69,46 @@ internal class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
binding.ilUrlInput.apply {
|
||||
prefixText = SettingsViewModel.AUTH_URL
|
||||
}
|
||||
|
||||
binding.etInput.apply {
|
||||
binding.etAccountIdInput.apply {
|
||||
imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
setOnClickListener { isCursorVisible = true }
|
||||
doOnTextChanged { input, _, _, _ ->
|
||||
viewModel.onValidateInput(input.toString())
|
||||
doOnTextChanged { accountId, _, _, _ ->
|
||||
viewModel.onValidateAccountId(accountId.toString())
|
||||
}
|
||||
requestFocus()
|
||||
}
|
||||
|
||||
binding.btLogin.setOnClickListener {
|
||||
binding.etAuthBaseUrlInput.apply {
|
||||
imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
setOnClickListener { isCursorVisible = true }
|
||||
doOnTextChanged { authBaseUrl, _, _, _ ->
|
||||
viewModel.onValidateAuthBaseUrl(authBaseUrl.toString())
|
||||
}
|
||||
}
|
||||
|
||||
binding.etApiUrlInput.apply {
|
||||
imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
setOnClickListener { isCursorVisible = true }
|
||||
doOnTextChanged { apiUrl, _, _, _ ->
|
||||
viewModel.onValidateApiUrl(apiUrl.toString())
|
||||
}
|
||||
}
|
||||
|
||||
binding.etLogFilterInput.apply {
|
||||
imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
setOnClickListener { isCursorVisible = true }
|
||||
doOnTextChanged { logFilter, _, _, _ ->
|
||||
viewModel.onValidateLogFilter(logFilter.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtonListeners() {
|
||||
binding.btSaveSettings.setOnClickListener {
|
||||
viewModel.onSaveSettingsCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtonListener() {
|
||||
binding.btCancel.setOnClickListener {
|
||||
viewModel.onCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
/* Licensed under Apache 2.0 (C) 2023 Firezone, Inc. */
|
||||
package dev.firezone.android.features.settings.ui
|
||||
|
||||
import android.webkit.URLUtil
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.firezone.android.BuildConfig
|
||||
import dev.firezone.android.core.domain.preference.GetConfigUseCase
|
||||
import dev.firezone.android.core.domain.preference.SaveAccountIdUseCase
|
||||
import dev.firezone.android.core.domain.preference.SaveSettingsUseCase
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -17,7 +19,7 @@ internal class SettingsViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val getConfigUseCase: GetConfigUseCase,
|
||||
private val saveAccountIdUseCase: SaveAccountIdUseCase,
|
||||
private val saveSettingsUseCase: SaveSettingsUseCase,
|
||||
) : ViewModel() {
|
||||
private val stateMutableLiveData = MutableLiveData<ViewState>()
|
||||
val stateLiveData: LiveData<ViewState> = stateMutableLiveData
|
||||
@@ -25,13 +27,21 @@ internal class SettingsViewModel
|
||||
private val actionMutableLiveData = MutableLiveData<ViewAction>()
|
||||
val actionLiveData: LiveData<ViewAction> = actionMutableLiveData
|
||||
|
||||
private var input = ""
|
||||
private var accountId = ""
|
||||
private var authBaseUrl = ""
|
||||
private var apiUrl = ""
|
||||
private var logFilter = ""
|
||||
|
||||
fun getAccountId() {
|
||||
fun populateFieldsFromConfig() {
|
||||
viewModelScope.launch {
|
||||
getConfigUseCase().collect {
|
||||
actionMutableLiveData.postValue(
|
||||
ViewAction.FillAccountId(it.accountId.orEmpty()),
|
||||
ViewAction.FillSettings(
|
||||
it.accountId.orEmpty(),
|
||||
it.authBaseUrl.orEmpty(),
|
||||
it.apiUrl.orEmpty(),
|
||||
it.logFilter.orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -39,32 +49,82 @@ internal class SettingsViewModel
|
||||
|
||||
fun onSaveSettingsCompleted() {
|
||||
viewModelScope.launch {
|
||||
saveAccountIdUseCase(input).collect {
|
||||
saveSettingsUseCase(accountId, authBaseUrl, apiUrl, logFilter).collect {
|
||||
actionMutableLiveData.postValue(ViewAction.NavigateToSignIn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onValidateInput(input: String) {
|
||||
this.input = input
|
||||
fun onCancel() {
|
||||
actionMutableLiveData.postValue(ViewAction.NavigateToSignIn)
|
||||
}
|
||||
|
||||
fun onValidateAccountId(accountId: String) {
|
||||
this.accountId = accountId
|
||||
stateMutableLiveData.postValue(
|
||||
ViewState().copy(
|
||||
isButtonEnabled = input.isEmpty().not(),
|
||||
isSaveButtonEnabled = areFieldsValid(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val AUTH_URL = "${BuildConfig.AUTH_SCHEME}://${BuildConfig.AUTH_HOST}:${BuildConfig.AUTH_PORT}/"
|
||||
fun onValidateAuthBaseUrl(authBaseUrl: String) {
|
||||
this.authBaseUrl = authBaseUrl
|
||||
stateMutableLiveData.postValue(
|
||||
ViewState().copy(
|
||||
isSaveButtonEnabled = areFieldsValid(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onValidateApiUrl(apiUrl: String) {
|
||||
this.apiUrl = apiUrl
|
||||
stateMutableLiveData.postValue(
|
||||
ViewState().copy(
|
||||
isSaveButtonEnabled = areFieldsValid(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onValidateLogFilter(logFilter: String) {
|
||||
this.logFilter = logFilter
|
||||
stateMutableLiveData.postValue(
|
||||
ViewState().copy(
|
||||
isSaveButtonEnabled = areFieldsValid(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal sealed class ViewAction {
|
||||
object NavigateToSignIn : ViewAction()
|
||||
|
||||
data class FillAccountId(val value: String) : ViewAction()
|
||||
data class FillSettings(
|
||||
val accountId: String,
|
||||
val authBaseUrl: String,
|
||||
val apiUrl: String,
|
||||
val logFilter: String,
|
||||
) : ViewAction()
|
||||
}
|
||||
|
||||
private fun areFieldsValid(): Boolean {
|
||||
// This comes from the backend account slug validator at elixir/apps/domain/lib/domain/accounts/account/changeset.ex
|
||||
val accountIdRegex = Regex("^[a-z0-9_]{3,100}\$")
|
||||
return accountIdRegex.matches(accountId) &&
|
||||
URLUtil.isValidUrl(authBaseUrl) &&
|
||||
isUriValid(apiUrl) &&
|
||||
logFilter.isNotBlank()
|
||||
}
|
||||
|
||||
private fun isUriValid(uri: String): Boolean {
|
||||
return try {
|
||||
URI(uri)
|
||||
true
|
||||
} catch (e: URISyntaxException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ViewState(
|
||||
val isButtonEnabled: Boolean = false,
|
||||
val isSaveButtonEnabled: Boolean = false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import com.google.firebase.installations.FirebaseInstallations
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.firezone.android.BuildConfig
|
||||
import dev.firezone.android.R
|
||||
import dev.firezone.android.core.data.PreferenceRepository
|
||||
import dev.firezone.android.core.domain.preference.GetConfigUseCase
|
||||
@@ -187,11 +186,11 @@ class TunnelService : VpnService() {
|
||||
|
||||
sessionPtr =
|
||||
TunnelSession.connect(
|
||||
controlPlaneUrl = BuildConfig.CONTROL_PLANE_URL,
|
||||
apiUrl = config.apiUrl,
|
||||
token = config.token,
|
||||
deviceId = deviceId(),
|
||||
logDir = getLogDir(),
|
||||
logFilter = BuildConfig.CONNLIB_LOG_FILTER_STRING,
|
||||
logFilter = config.logFilter,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package dev.firezone.android.tunnel
|
||||
|
||||
object TunnelSession {
|
||||
external fun connect(
|
||||
controlPlaneUrl: String,
|
||||
apiUrl: String,
|
||||
token: String,
|
||||
deviceId: String,
|
||||
logDir: String,
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="AUTH Complete"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:text="Signed in"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tvSignStatus"
|
||||
android:id="@+id/tvLaunchingAuthFlow"
|
||||
style="@style/AppTheme.Base.Body1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tvSignStatus"
|
||||
android:id="@+id/tvResourcesList"
|
||||
style="@style/AppTheme.Base.H5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -43,10 +43,10 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/llContainer" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/resourcesList"
|
||||
android:id="@+id/rvResourcesList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvSignStatus"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvResourcesList"
|
||||
app:layout_constraintBottom_toTopOf="@id/btSignOut" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/spacing_medium">
|
||||
@@ -31,36 +32,157 @@
|
||||
android:text="@string/app_short_name" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/ilUrlInput"
|
||||
android:layout_width="0dp"
|
||||
<TextView
|
||||
android:id="@+id/tvSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true"
|
||||
android:hint="@string/enter_team_id"
|
||||
android:text="@string/settings_title"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.225" />
|
||||
<TextView
|
||||
android:id="@+id/tvRequiredSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/required_settings_title"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.335" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/ilAccountIdInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.42">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etAccountIdInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/account_id"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAdvancedSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/advanced_settings_title"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.54" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/ilAuthBaseUrlInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.66">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etAuthBaseUrlInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/auth_base_url"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/ilApiUrlInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.78">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etInput"
|
||||
android:id="@+id/etApiUrlInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/account_id"
|
||||
android:hint="@string/api_url"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btLogin"
|
||||
android:layout_width="0dp"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/ilLogFilterInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.90">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etLogFilterInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/log_filter"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btSaveSettings"
|
||||
android:layout_width="135dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/save"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btCancel"
|
||||
android:layout_width="135dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:enabled="true"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tvSignStatus"
|
||||
android:id="@+id/tvSessionStatus"
|
||||
style="@style/AppTheme.Base.HeaderText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -47,20 +47,24 @@
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btSignIn"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="135dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/sign_in"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btSettings"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btSettings"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="135dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/settings"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
<string name="app_short_name">firezone</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="account_id">account-id</string>
|
||||
<string name="settings_title">Settings</string>
|
||||
<string name="required_settings_title">Required</string>
|
||||
<string name="advanced_settings_title">Advanced</string>
|
||||
<string name="account_id">Account ID</string>
|
||||
<string name="auth_base_url">Auth Base URL</string>
|
||||
<string name="api_url">API URL</string>
|
||||
<string name="log_filter">Log Filter</string>
|
||||
<string name="save">Save</string>
|
||||
|
||||
|
||||
<!-- Sign In -->
|
||||
<string name="sign_in">Sign In</string>
|
||||
<string name="settings">Settings</string>
|
||||
@@ -15,18 +22,14 @@
|
||||
<string name="sign_out">Sign Out</string>
|
||||
|
||||
<!-- Auth -->
|
||||
<string name="launching_auth_flow">Launching Auth Flow…</string>
|
||||
<string name="launching_auth_flow">Launching Chrome to sign in...</string>
|
||||
|
||||
<!-- Error Dialog -->
|
||||
<string name="error_dialog_title">Error</string>
|
||||
<string name="error_dialog_message">Ops, something went wrong. Please try again later.</string>
|
||||
<string name="error_dialog_message">Oops! Something went wrong. Contact your admin if this issue persists.</string>
|
||||
<string name="error_dialog_button_text">Ok</string>
|
||||
<string name="enable_vpn_permission">Enable VPN Permission</string>
|
||||
<string name="vpn_permission_description">Firezone requires the VPN permission in order to route packets from your device to protected resources in a secure manner. All communication is end-to-end encrypted; we can never decrypt or otherwise monitor your communication. Please grant the VPN permission by tapping the button below.</string>
|
||||
<string name="request_permission">Request Permission</string>
|
||||
<string name="enter_team_id">Enter team id</string>
|
||||
<string name="sign_in_debug_user">Sign In (Debug User)</string>
|
||||
<string name="signing_in_requires_chrome_browser">Signing in requires chrome browser</string>
|
||||
<!-- Error Dialog -->
|
||||
|
||||
<string name="signing_in_requires_chrome_browser">Signing in requires Chrome browser</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copy this file to local.properties and define the variables below.
|
||||
#
|
||||
# Location of the SDK. This is only used by Gradle. For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
# e.g. /Users/jamil/Library/Android/sdk
|
||||
sdk.dir=
|
||||
|
||||
# Auth token from portal to use for debugUser in order to bypass the auth flow and send to connlib.
|
||||
token=
|
||||
@@ -394,15 +394,15 @@ macro_rules! string_from_jstring {
|
||||
|
||||
fn connect(
|
||||
env: &mut JNIEnv,
|
||||
portal_url: JString,
|
||||
portal_token: JString,
|
||||
api_url: JString,
|
||||
token: JString,
|
||||
device_id: JString,
|
||||
log_dir: JString,
|
||||
log_filter: JString,
|
||||
callback_handler: GlobalRef,
|
||||
) -> Result<Session<CallbackHandler>, ConnectError> {
|
||||
let portal_url = string_from_jstring!(env, portal_url);
|
||||
let secret = SecretString::from(string_from_jstring!(env, portal_token));
|
||||
let api_url = string_from_jstring!(env, api_url);
|
||||
let secret = SecretString::from(string_from_jstring!(env, token));
|
||||
let device_id = string_from_jstring!(env, device_id);
|
||||
let log_dir = string_from_jstring!(env, log_dir);
|
||||
let log_filter = string_from_jstring!(env, log_filter);
|
||||
@@ -415,7 +415,7 @@ fn connect(
|
||||
handle,
|
||||
};
|
||||
|
||||
let session = Session::connect(portal_url.as_str(), secret, device_id, callback_handler)?;
|
||||
let session = Session::connect(api_url.as_str(), secret, device_id, callback_handler)?;
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
@@ -428,8 +428,8 @@ fn connect(
|
||||
pub unsafe extern "system" fn Java_dev_firezone_android_tunnel_TunnelSession_connect(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
portal_url: JString,
|
||||
portal_token: JString,
|
||||
api_url: JString,
|
||||
token: JString,
|
||||
device_id: JString,
|
||||
log_dir: JString,
|
||||
log_filter: JString,
|
||||
@@ -442,8 +442,8 @@ pub unsafe extern "system" fn Java_dev_firezone_android_tunnel_TunnelSession_con
|
||||
let connect = catch_and_throw(&mut env, |env| {
|
||||
connect(
|
||||
env,
|
||||
portal_url,
|
||||
portal_token,
|
||||
api_url,
|
||||
token,
|
||||
device_id,
|
||||
log_dir,
|
||||
log_filter,
|
||||
|
||||
@@ -20,7 +20,7 @@ mod ffi {
|
||||
|
||||
#[swift_bridge(associated_to = WrappedSession)]
|
||||
fn connect(
|
||||
portal_url: String,
|
||||
api_url: String,
|
||||
token: String,
|
||||
device_id: String,
|
||||
log_dir: String,
|
||||
@@ -162,7 +162,7 @@ fn init_logging(log_dir: PathBuf, log_filter: String) -> file_logger::Handle {
|
||||
|
||||
impl WrappedSession {
|
||||
fn connect(
|
||||
portal_url: String,
|
||||
api_url: String,
|
||||
token: String,
|
||||
device_id: String,
|
||||
log_dir: String,
|
||||
@@ -172,7 +172,7 @@ impl WrappedSession {
|
||||
let secret = SecretString::from(token);
|
||||
|
||||
let session = Session::connect(
|
||||
portal_url.as_str(),
|
||||
api_url.as_str(),
|
||||
secret,
|
||||
device_id,
|
||||
CallbackHandler {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Main connlib library for clients.
|
||||
pub use connlib_shared::{get_device_id, messages::ResourceDescription};
|
||||
pub use connlib_shared::messages::ResourceDescription;
|
||||
pub use connlib_shared::{Callbacks, Error};
|
||||
pub use tracing_appender::non_blocking::WorkerGuard;
|
||||
|
||||
@@ -60,7 +60,7 @@ where
|
||||
/// On a fatal error you should call `[Session::disconnect]` and start a new one.
|
||||
// TODO: token should be something like SecretString but we need to think about FFI compatibility
|
||||
pub fn connect(
|
||||
portal_url: impl TryInto<Url>,
|
||||
api_url: impl TryInto<Url>,
|
||||
token: SecretString,
|
||||
device_id: String,
|
||||
callbacks: CB,
|
||||
@@ -105,7 +105,7 @@ where
|
||||
Self::connect_inner(
|
||||
&runtime,
|
||||
tx,
|
||||
portal_url.try_into().map_err(|_| Error::UriError)?,
|
||||
api_url.try_into().map_err(|_| Error::UriError)?,
|
||||
token,
|
||||
device_id,
|
||||
this.callbacks.clone(),
|
||||
@@ -121,14 +121,14 @@ where
|
||||
fn connect_inner(
|
||||
runtime: &Runtime,
|
||||
runtime_stopper: tokio::sync::mpsc::Sender<StopRuntime>,
|
||||
portal_url: Url,
|
||||
api_url: Url,
|
||||
token: SecretString,
|
||||
device_id: String,
|
||||
callbacks: CallbackErrorFacade<CB>,
|
||||
) {
|
||||
runtime.spawn(async move {
|
||||
let (connect_url, private_key) = fatal_error!(
|
||||
login_url(Mode::Client, portal_url, token, device_id),
|
||||
login_url(Mode::Client, api_url, token, device_id),
|
||||
runtime_stopper,
|
||||
&callbacks
|
||||
);
|
||||
|
||||
@@ -31,8 +31,8 @@ const LIB_NAME: &str = "connlib";
|
||||
/// Creates a new login URL to use with the portal.
|
||||
pub fn login_url(
|
||||
mode: Mode,
|
||||
portal_url: Url,
|
||||
portal_token: SecretString,
|
||||
api_url: Url,
|
||||
token: SecretString,
|
||||
device_id: String,
|
||||
) -> Result<(Url, StaticSecret)> {
|
||||
let private_key = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
||||
@@ -44,8 +44,8 @@ pub fn login_url(
|
||||
let external_id = sha256(device_id);
|
||||
|
||||
let url = get_websocket_path(
|
||||
portal_url,
|
||||
portal_token,
|
||||
api_url,
|
||||
token,
|
||||
match mode {
|
||||
Mode::Client => "client",
|
||||
Mode::Gateway => "gateway",
|
||||
@@ -73,36 +73,6 @@ pub fn get_user_agent() -> String {
|
||||
format!("{os_type}/{os_version} {lib_name}/{lib_version}")
|
||||
}
|
||||
|
||||
/// Returns the SMBios Serial of the device or a random UUIDv4 if the SMBios is not available.
|
||||
#[cfg(not(any(target_os = "ios", target_os = "android")))]
|
||||
pub fn get_device_id() -> String {
|
||||
match smbioslib::table_load_from_device() {
|
||||
Ok(data) => {
|
||||
if let Some(uuid) =
|
||||
data.find_map(|sys_info: smbioslib::SMBiosSystemInformation| sys_info.uuid())
|
||||
{
|
||||
tracing::debug!("get_device_id() found SMBios Serial: {}", uuid);
|
||||
return uuid.to_string();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("get_device_id() couldn't load SMBios. Error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::warn!("get_device_id() couldn't find a SMBios Serial. Using random UUIDv4 instead.");
|
||||
uuid::Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "android"))]
|
||||
pub fn get_device_id() -> String {
|
||||
tracing::warn!(
|
||||
"get_device_id() is not implemented for this platform. Using random UUIDv4 instead."
|
||||
);
|
||||
|
||||
uuid::Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
fn set_ws_scheme(url: &mut Url) -> Result<()> {
|
||||
let scheme = match url.scheme() {
|
||||
"http" | "ws" => "ws",
|
||||
@@ -128,24 +98,24 @@ fn sha256(input: String) -> String {
|
||||
}
|
||||
|
||||
fn get_websocket_path(
|
||||
mut url: Url,
|
||||
mut api_url: Url,
|
||||
secret: SecretString,
|
||||
mode: &str,
|
||||
public_key: &Key,
|
||||
external_id: &str,
|
||||
name_suffix: &str,
|
||||
) -> Result<Url> {
|
||||
set_ws_scheme(&mut url)?;
|
||||
set_ws_scheme(&mut api_url)?;
|
||||
|
||||
{
|
||||
let mut paths = url.path_segments_mut().map_err(|_| Error::UriError)?;
|
||||
let mut paths = api_url.path_segments_mut().map_err(|_| Error::UriError)?;
|
||||
paths.pop_if_empty();
|
||||
paths.push(mode);
|
||||
paths.push("websocket");
|
||||
}
|
||||
|
||||
{
|
||||
let mut query_pairs = url.query_pairs_mut();
|
||||
let mut query_pairs = api_url.query_pairs_mut();
|
||||
query_pairs.clear();
|
||||
query_pairs.append_pair("token", secret.expose_secret());
|
||||
query_pairs.append_pair("public_key", &public_key.to_string());
|
||||
@@ -153,5 +123,5 @@ fn get_websocket_path(
|
||||
query_pairs.append_pair("name_suffix", name_suffix);
|
||||
}
|
||||
|
||||
Ok(url)
|
||||
Ok(api_url)
|
||||
}
|
||||
|
||||
@@ -24,15 +24,18 @@ where
|
||||
/// Arguments common to all Firezone CLI components.
|
||||
#[derive(Args, Clone)]
|
||||
pub struct CommonArgs {
|
||||
/// Firezone admin portal websocket URL
|
||||
#[arg(
|
||||
short = 'u',
|
||||
long,
|
||||
env = "PORTAL_URL",
|
||||
hide = true,
|
||||
env = "FIREZONE_API_URL",
|
||||
default_value = "wss://api.firezone.dev"
|
||||
)]
|
||||
pub portal_url: Url,
|
||||
pub api_url: Url,
|
||||
/// Identifier generated by the portal to identify and display the device.
|
||||
#[arg(short = 'i', long, env = "FIREZONE_ID")]
|
||||
pub firezone_id: String,
|
||||
/// Token generated by the portal to authorize websocket connection.
|
||||
#[arg(short = 't', long, env = "PORTAL_TOKEN")]
|
||||
pub portal_token: String,
|
||||
#[arg(env = "FIREZONE_TOKEN")]
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
@@ -10,17 +10,25 @@ You should then find a binary in `target/release/firezone-gateway`.
|
||||
|
||||
## Running
|
||||
|
||||
To run the gateway:
|
||||
The Firezone Gateway supports Linux only. To run the Gateway binary on your
|
||||
Linux host:
|
||||
|
||||
1. Generate a new Gateway token from the "Gateways" section of the admin portal
|
||||
and save it in your secrets manager.
|
||||
1. Ensure the `FIREZONE_TOKEN=<gateway_token>` environment variable is set
|
||||
securely in your Gateway's shell environment. The Gateway requires this
|
||||
variable at startup.
|
||||
1. Set `FIREZONE_ID` to a unique string to identify this gateway in the portal,
|
||||
e.g. `export FIREZONE_ID=$(uuidgen)`. The Gateway requires this variable at
|
||||
startup.
|
||||
1. Now, you can start the Gateway with:
|
||||
|
||||
```
|
||||
firezone-gateway --portal_token <portal_token>
|
||||
firezone-gateway
|
||||
```
|
||||
|
||||
where `portal_token` is the token shown when creating a gateway group in the
|
||||
Firezone admin portal.
|
||||
|
||||
If you're running as an unprivileged user, you'll need the `CAP_NET_ADMIN`
|
||||
capability to open `/dev/net/tun`. You can add this to the gateway binary with:
|
||||
If you're running as a non-root user, you'll need the `CAP_NET_ADMIN` capability
|
||||
to open `/dev/net/tun`. You can add this to the gateway binary with:
|
||||
|
||||
```
|
||||
sudo setcap 'cap_net_admin+eip' /path/to/firezone-gateway
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::messages::InitGateway;
|
||||
use anyhow::{Context, Result};
|
||||
use backoff::ExponentialBackoffBuilder;
|
||||
use clap::Parser;
|
||||
use connlib_shared::{get_device_id, get_user_agent, login_url, Callbacks, Mode};
|
||||
use connlib_shared::{get_user_agent, login_url, Callbacks, Mode};
|
||||
use firezone_cli_utils::{setup_global_subscriber, CommonArgs};
|
||||
use firezone_tunnel::{GatewayState, Tunnel};
|
||||
use futures::{future, TryFutureExt};
|
||||
@@ -27,9 +27,9 @@ async fn main() -> Result<()> {
|
||||
|
||||
let (connect_url, private_key) = login_url(
|
||||
Mode::Gateway,
|
||||
cli.common.portal_url,
|
||||
SecretString::new(cli.common.portal_token),
|
||||
get_device_id(),
|
||||
cli.common.api_url,
|
||||
SecretString::new(cli.common.token),
|
||||
cli.common.firezone_id,
|
||||
)?;
|
||||
let tunnel = Arc::new(Tunnel::new(private_key, CallbackHandler).await?);
|
||||
|
||||
|
||||
@@ -4,22 +4,39 @@ This crate houses the Firezone linux client.
|
||||
|
||||
## Building
|
||||
|
||||
You can build the linux client using:
|
||||
`cargo build --release --bin firezone-linux-client`
|
||||
Assuming you have Rust installed, you can build the Linux client from a Linux
|
||||
host with:
|
||||
|
||||
```
|
||||
cargo build --release --bin firezone-linux-client
|
||||
```
|
||||
|
||||
You should then find a binary in `target/release/firezone-linux-client`.
|
||||
|
||||
## Running
|
||||
|
||||
To run the linux client:
|
||||
To run the Linux client:
|
||||
|
||||
1. Generate a new Service account token from the "Actors -> Service Accounts"
|
||||
section of the admin portal and save it in your secrets manager. The Firezone
|
||||
Linux client requires a service account at this time.
|
||||
1. Ensure the `FIREZONE_TOKEN=<service_account_token>` environment variable is
|
||||
set securely in your client's shell environment. The client requires this
|
||||
variable at startup.
|
||||
1. Set `FIREZONE_ID` to a unique string to identify this client in the portal,
|
||||
e.g. `export FIREZONE_ID=$(uuidgen)`. The client requires this variable at
|
||||
startup.
|
||||
1. Set `LOG_DIR` to a suitable directory for writing logs
|
||||
```
|
||||
export LOG_DIR=/tmp/firezone-logs
|
||||
mkdir $LOG_DIR
|
||||
```
|
||||
1. Now, you can start the client with:
|
||||
|
||||
```
|
||||
firezone-linux-client --portal_token <portal_token>
|
||||
./firezone-linux-client
|
||||
```
|
||||
|
||||
where `portal_token` is the token shown when creating a client group in the
|
||||
Firezone admin portal.
|
||||
|
||||
If you're running as an unprivileged user, you'll need the `CAP_NET_ADMIN`
|
||||
capability to open `/dev/net/tun`. You can add this to the client binary with:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use connlib_client_shared::{file_logger, get_device_id, Callbacks, Error, Session};
|
||||
use connlib_client_shared::{file_logger, Callbacks, Error, Session};
|
||||
use firezone_cli_utils::{block_on_ctrl_c, setup_global_subscriber, CommonArgs};
|
||||
use secrecy::SecretString;
|
||||
use std::path::PathBuf;
|
||||
@@ -11,12 +11,10 @@ fn main() -> Result<()> {
|
||||
let (layer, handle) = cli.log_dir.as_deref().map(file_logger::layer).unzip();
|
||||
setup_global_subscriber(layer);
|
||||
|
||||
let device_id = get_device_id();
|
||||
|
||||
let mut session = Session::connect(
|
||||
cli.common.portal_url,
|
||||
SecretString::from(cli.common.portal_token),
|
||||
device_id,
|
||||
cli.common.api_url,
|
||||
SecretString::from(cli.common.token),
|
||||
cli.common.firezone_id,
|
||||
CallbackHandler { handle },
|
||||
)
|
||||
.unwrap();
|
||||
@@ -55,7 +53,7 @@ struct Cli {
|
||||
#[command(flatten)]
|
||||
common: CommonArgs,
|
||||
|
||||
/// File logging directory.
|
||||
#[arg(short, long, env = "FZ_LOG_DIR")]
|
||||
/// File logging directory. Should be a path that's writeable by the current user.
|
||||
#[arg(short, long, env = "LOG_DIR")]
|
||||
log_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -22,19 +22,25 @@ You should then find a binary in `target/release/firezone-relay`.
|
||||
|
||||
## Running
|
||||
|
||||
To run the relay:
|
||||
The Firezone Relay supports Linux only. To run the Relay binary on your Linux
|
||||
host:
|
||||
|
||||
1. Generate a new Relay token from the "Relays" section of the admin portal and
|
||||
save it in your secrets manager.
|
||||
1. Ensure the `FIREZONE_TOKEN=<relay_token>` environment variable is set
|
||||
securely in your Relay's shell environment. The Relay expects this variable
|
||||
at startup.
|
||||
1. Now, you can start the Firezone Relay with:
|
||||
|
||||
```
|
||||
firezone-relay --portal_token <portal_token>
|
||||
firezone-relay
|
||||
```
|
||||
|
||||
where `portal_token` is the token shown when creating a Relay in the Firezone
|
||||
admin portal.
|
||||
To view more advanced configuration options pass the `--help` flag:
|
||||
|
||||
For an up-to-date documentation on the available configurations options and a
|
||||
detailed help text, run `cargo run --bin relay -- --help`. All command-line
|
||||
options can be overridden using environment variables. Those variables are
|
||||
listed in the `--help` output at the bottom of each command.
|
||||
```
|
||||
firezone-relay --help
|
||||
```
|
||||
|
||||
### Ports
|
||||
|
||||
@@ -44,9 +50,8 @@ not configurable. Additionally, the relay needs to have access to the port range
|
||||
|
||||
### Portal Connection
|
||||
|
||||
When given a `portal_token`, the relay will connect to the Firezone portal
|
||||
(default `wss://api.firezone.dev`) and wait for an `init` message before
|
||||
commencing relay operations.
|
||||
When given a `token`, the relay will connect to the Firezone portal and wait for
|
||||
an `init` message before commencing relay operations.
|
||||
|
||||
## Design
|
||||
|
||||
|
||||
@@ -36,44 +36,48 @@ struct Args {
|
||||
/// The address of the local interface where we should serve our health-check endpoint.
|
||||
///
|
||||
/// The actual health-check endpoint will be at `http://<health_check_addr>/healthz`.
|
||||
#[arg(long, env, default_value = "0.0.0.0:8080")]
|
||||
#[arg(long, env, hide = true, default_value = "0.0.0.0:8080")]
|
||||
health_check_addr: SocketAddr,
|
||||
// See https://www.rfc-editor.org/rfc/rfc8656.html#name-allocations
|
||||
/// The lowest port used for TURN allocations.
|
||||
#[arg(long, env, default_value = "49152")]
|
||||
#[arg(long, env, hide = true, default_value = "49152")]
|
||||
lowest_port: u16,
|
||||
/// The highest port used for TURN allocations.
|
||||
#[arg(long, env, default_value = "65535")]
|
||||
#[arg(long, env, hide = true, default_value = "65535")]
|
||||
highest_port: u16,
|
||||
/// Firezone admin portal websocket URL
|
||||
#[arg(long, env, default_value = "wss://api.firezone.dev")]
|
||||
portal_url: Url,
|
||||
#[arg(
|
||||
long,
|
||||
env = "FIREZONE_API_URL",
|
||||
hide = true,
|
||||
default_value = "wss://api.firezone.dev"
|
||||
)]
|
||||
api_url: Url,
|
||||
/// Token generated by the portal to authorize websocket connection.
|
||||
///
|
||||
/// If omitted, we won't connect to the portal on startup.
|
||||
#[arg(long, env)]
|
||||
portal_token: Option<SecretString>,
|
||||
#[arg(env = "FIREZONE_TOKEN")]
|
||||
token: Option<SecretString>,
|
||||
/// A seed to use for all randomness operations.
|
||||
///
|
||||
/// Only available in debug builds.
|
||||
#[arg(long, env)]
|
||||
#[arg(long, env, hide = true)]
|
||||
rng_seed: Option<u64>,
|
||||
|
||||
/// How to format the logs.
|
||||
#[arg(long, env, default_value = "human")]
|
||||
#[arg(long, env, default_value = "human", hide = true)]
|
||||
log_format: LogFormat,
|
||||
|
||||
/// Which OTLP collector we should connect to.
|
||||
///
|
||||
/// If set, we will report traces and metrics to this collector via gRPC.
|
||||
#[arg(long, env)]
|
||||
#[arg(long, env, hide = true)]
|
||||
otlp_grpc_endpoint: Option<SocketAddr>,
|
||||
|
||||
/// The Google Project ID to embed in spans.
|
||||
///
|
||||
/// Set this if you are running on Google Cloud but using the OTLP trace collector.
|
||||
/// OTLP is vendor-agnostic but for spans to be correctly recognised by Google Cloud, they need the project ID to be set.
|
||||
#[arg(long, env)]
|
||||
#[arg(long, env, hide = true)]
|
||||
google_cloud_project_id: Option<String>,
|
||||
}
|
||||
|
||||
@@ -106,8 +110,8 @@ async fn main() -> Result<()> {
|
||||
args.highest_port,
|
||||
);
|
||||
|
||||
let channel = if let Some(token) = args.portal_token.as_ref() {
|
||||
let base_url = args.portal_url.clone();
|
||||
let channel = if let Some(token) = args.token.as_ref() {
|
||||
let base_url = args.api_url.clone();
|
||||
let stamp_secret = server.auth_secret();
|
||||
|
||||
let span = tracing::error_span!("connect_to_portal", config_url = %base_url);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Apple Developer account-specific configuration
|
||||
DEVELOPMENT_TEAM = 47R2M6779T
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.firezone.firezone
|
||||
APP_GROUP_ID[sdk=macosx*] = 47R2M6779T.group.dev.firezone.firezone
|
||||
APP_GROUP_ID[sdk=iphoneos*] = group.dev.firezone.firezone
|
||||
CODE_SIGN_STYLE = Manual
|
||||
CODE_SIGN_IDENTITY = Apple Distribution: Firezone, Inc. (47R2M6779T)
|
||||
IOS_APP_PROVISIONING_PROFILE_IDENTIFIER = b32a5853-699d-4f19-85d3-5b13b1ac5dbb
|
||||
MACOS_APP_PROVISIONING_PROFILE_IDENTIFIER = 70055d90-0252-4ee5-a60c-4d6f3840ee62
|
||||
IOS_NE_PROVISIONING_PROFILE_IDENTIFIER = 23402aaa-f72c-4947-a795-23a9cf495968
|
||||
MACOS_NE_PROVISIONING_PROFILE_IDENTIFIER = 1e965794-0b2c-46d3-a955-d96aacf25546
|
||||
|
||||
// Portal Auth
|
||||
AUTH_URL_SCHEME = https
|
||||
AUTH_URL_HOST = app.firez.one
|
||||
CONTROL_PLANE_URL_SCHEME = wss
|
||||
CONTROL_PLANE_URL_HOST = api.firez.one
|
||||
@@ -30,7 +30,7 @@ CI/CD pipeline.
|
||||
1. Copy an appropriate xcconfig and edit as necessary:
|
||||
|
||||
```bash
|
||||
cp Firezone/xcconfig/dev.xcconfig Firezone/xcconfig/config.xcconfig
|
||||
cp Firezone/xcconfig/debug.xcconfig Firezone/xcconfig/config.xcconfig
|
||||
vim Firezone/xcconfig/config.xcconfig
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ resource "google_compute_subnetwork" "gateways" {
|
||||
}
|
||||
|
||||
module "gateways" {
|
||||
count = var.gateway_portal_token != null ? 1 : 0
|
||||
count = var.gateway_token != null ? 1 : 0
|
||||
|
||||
source = "../../modules/gateway-google-cloud-compute"
|
||||
project_id = module.google-cloud-project.project.project_id
|
||||
@@ -74,14 +74,14 @@ module "gateways" {
|
||||
}
|
||||
}
|
||||
|
||||
portal_websocket_url = "wss://api.${local.tld}"
|
||||
portal_token = var.gateway_portal_token
|
||||
api_url = "wss://api.${local.tld}"
|
||||
token = var.gateway_token
|
||||
}
|
||||
|
||||
|
||||
# Allow inbound traffic
|
||||
# resource "google_compute_firewall" "ingress-ipv4" {
|
||||
# count = var.gateway_portal_token != null ? 1 : 0
|
||||
# count = var.gateway_token != null ? 1 : 0
|
||||
|
||||
# project = module.google-cloud-project.project.project_id
|
||||
|
||||
@@ -98,7 +98,7 @@ module "gateways" {
|
||||
# }
|
||||
|
||||
# resource "google_compute_firewall" "ingress-ipv6" {
|
||||
# count = var.gateway_portal_token != null ? 1 : 0
|
||||
# count = var.gateway_token != null ? 1 : 0
|
||||
|
||||
# project = module.google-cloud-project.project.project_id
|
||||
|
||||
@@ -116,7 +116,7 @@ module "gateways" {
|
||||
|
||||
# Allow outbound traffic
|
||||
resource "google_compute_firewall" "egress-ipv4" {
|
||||
count = var.gateway_portal_token != null ? 1 : 0
|
||||
count = var.gateway_token != null ? 1 : 0
|
||||
|
||||
project = module.google-cloud-project.project.project_id
|
||||
|
||||
@@ -133,7 +133,7 @@ resource "google_compute_firewall" "egress-ipv4" {
|
||||
}
|
||||
|
||||
resource "google_compute_firewall" "egress-ipv6" {
|
||||
count = var.gateway_portal_token != null ? 1 : 0
|
||||
count = var.gateway_token != null ? 1 : 0
|
||||
|
||||
project = module.google-cloud-project.project.project_id
|
||||
|
||||
|
||||
@@ -619,7 +619,7 @@ resource "google_project_iam_member" "application" {
|
||||
|
||||
# Deploy relays
|
||||
module "relays" {
|
||||
count = var.relay_portal_token != null ? 1 : 0
|
||||
count = var.relay_token != null ? 1 : 0
|
||||
|
||||
source = "../../modules/relay-app"
|
||||
project_id = module.google-cloud-project.project.project_id
|
||||
@@ -708,8 +708,8 @@ module "relays" {
|
||||
}
|
||||
}
|
||||
|
||||
portal_websocket_url = "wss://api.${local.tld}"
|
||||
portal_token = var.relay_portal_token
|
||||
api_url = "wss://api.${local.tld}"
|
||||
token = var.relay_token
|
||||
|
||||
depends_on = [
|
||||
module.api
|
||||
|
||||
@@ -3,12 +3,12 @@ variable "image_tag" {
|
||||
description = "Image tag for all services. Notice: we assume all services are deployed with the same version"
|
||||
}
|
||||
|
||||
variable "relay_portal_token" {
|
||||
variable "relay_token" {
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "gateway_portal_token" {
|
||||
variable "gateway_token" {
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
@@ -627,7 +627,7 @@ resource "google_project_iam_member" "application" {
|
||||
|
||||
# Deploy relays
|
||||
module "relays" {
|
||||
count = var.relay_portal_token != null ? 1 : 0
|
||||
count = var.relay_token != null ? 1 : 0
|
||||
|
||||
source = "../../modules/relay-app"
|
||||
project_id = module.google-cloud-project.project.project_id
|
||||
@@ -716,8 +716,8 @@ module "relays" {
|
||||
}
|
||||
}
|
||||
|
||||
portal_websocket_url = "wss://api.${local.tld}"
|
||||
portal_token = var.relay_portal_token
|
||||
api_url = "wss://api.${local.tld}"
|
||||
token = var.relay_token
|
||||
}
|
||||
|
||||
# Enable SSH on staging
|
||||
|
||||
@@ -3,7 +3,7 @@ variable "image_tag" {
|
||||
description = "Image tag for all services. Notice: we assume all services are deployed with the same version"
|
||||
}
|
||||
|
||||
variable "relay_portal_token" {
|
||||
variable "relay_token" {
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
@@ -40,12 +40,12 @@ locals {
|
||||
value = "127.0.0.1:4317"
|
||||
},
|
||||
{
|
||||
name = "PORTAL_TOKEN"
|
||||
value = var.portal_token
|
||||
name = "FIREZONE_TOKEN"
|
||||
value = var.token
|
||||
},
|
||||
{
|
||||
name = "PORTAL_URL"
|
||||
value = var.portal_websocket_url
|
||||
name = "FIREZONE_API_URL"
|
||||
value = var.api_url
|
||||
}
|
||||
], var.application_environment_variables)
|
||||
}
|
||||
|
||||
@@ -148,12 +148,12 @@ variable "application_environment_variables" {
|
||||
## Firezone
|
||||
################################################################################
|
||||
|
||||
variable "portal_token" {
|
||||
variable "token" {
|
||||
type = string
|
||||
description = "Portal token to use for authentication."
|
||||
}
|
||||
|
||||
variable "portal_websocket_url" {
|
||||
variable "api_url" {
|
||||
type = string
|
||||
default = "wss://api.firezone.dev"
|
||||
description = "URL of the control plane endpoint."
|
||||
|
||||
@@ -38,12 +38,12 @@ locals {
|
||||
value = "127.0.0.1:4317"
|
||||
},
|
||||
{
|
||||
name = "PORTAL_TOKEN"
|
||||
value = var.portal_token
|
||||
name = "FIREZONE_TOKEN"
|
||||
value = var.token
|
||||
},
|
||||
{
|
||||
name = "PORTAL_URL"
|
||||
value = var.portal_websocket_url
|
||||
name = "FIREZONE_API_URL"
|
||||
value = var.api_url
|
||||
}
|
||||
], var.application_environment_variables)
|
||||
}
|
||||
|
||||
@@ -133,12 +133,12 @@ variable "application_environment_variables" {
|
||||
## Firezone
|
||||
################################################################################
|
||||
|
||||
variable "portal_token" {
|
||||
variable "token" {
|
||||
type = string
|
||||
description = "Portal token to use for authentication."
|
||||
}
|
||||
|
||||
variable "portal_websocket_url" {
|
||||
variable "api_url" {
|
||||
type = string
|
||||
default = "wss://api.firezone.dev"
|
||||
description = "URL of the control plane endpoint."
|
||||
|
||||
Reference in New Issue
Block a user