From acb69864ec4694cb3e2e77a51791fdd59f3868f1 Mon Sep 17 00:00:00 2001 From: ekoby <7406535+ekoby@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:10:05 -0400 Subject: [PATCH] MFA support (#169) * add MFA data types * MFA client API * implement MFA enrollment, verification, and auth * provide MFA interaction sample * add retrieval/generation of recovery codes --- .../main/kotlin/org/openziti/ZitiEnroller.kt | 104 +++++++++++++++++- .../main/kotlin/org/openziti/Exceptions.kt | 5 +- ziti/src/main/kotlin/org/openziti/Ziti.kt | 19 +++- .../main/kotlin/org/openziti/ZitiContext.kt | 21 ++++ .../kotlin/org/openziti/api/Controller.kt | 58 +++++++++- ziti/src/main/kotlin/org/openziti/api/mfa.kt | 46 ++++++++ .../src/main/kotlin/org/openziti/api/types.kt | 8 +- .../org/openziti/impl/ZitiContextImpl.kt | 48 +++++++- .../main/kotlin/org/openziti/impl/ZitiImpl.kt | 20 ++-- .../openziti/posture/DefaultPostureService.kt | 11 +- 10 files changed, 313 insertions(+), 27 deletions(-) create mode 100644 ziti/src/main/kotlin/org/openziti/api/mfa.kt diff --git a/samples/ziti-enroller/src/main/kotlin/org/openziti/ZitiEnroller.kt b/samples/ziti-enroller/src/main/kotlin/org/openziti/ZitiEnroller.kt index 17203c48..ac04c668 100644 --- a/samples/ziti-enroller/src/main/kotlin/org/openziti/ZitiEnroller.kt +++ b/samples/ziti-enroller/src/main/kotlin/org/openziti/ZitiEnroller.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 NetFoundry, Inc. + * Copyright (c) 2018-2021 NetFoundry, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,18 +17,31 @@ package org.openziti import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.required import com.github.ajalt.clikt.parameters.options.validate import com.github.ajalt.clikt.parameters.types.file +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.openziti.api.MFAType import org.openziti.identity.Enroller +import java.io.File import java.io.FileNotFoundException import java.net.InetAddress import java.security.KeyStore +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage object ZitiEnroller { - private class Cli : CliktCommand(name = "ziti-enroller") { + private class enroll: CliktCommand() { val jwt by option(help = "Enrollment token (JWT file). Required").file().required().validate { it.exists() || throw FileNotFoundException("jwt[${it.path}] not found") } @@ -48,6 +61,93 @@ object ZitiEnroller { } } + private class verify: CliktCommand(help = "verify identity file. OTP will be requested if identity is enrolled in MFA") { + val idFile by option(help = "identity configuration file.").file().required().validate { + it.exists() + } + + val showCodes by option(help = "display recovery codes").flag(default = false) + val newCodes by option(help = "generate new recovery codes").flag(default = false) + + override fun run() { + val ztx = Ziti.newContext(idFile, charArrayOf(), object : Ziti.AuthHandler{ + override fun getCode(ztx: ZitiContext, mfaType: MFAType, provider: String) = + CompletableFuture.supplyAsync { + print("Enter MFA code for $mfaType/$provider[${ztx.getId()?.name}]: ") + val code = readLine() + code!! + } + }) + + val j = GlobalScope.launch { + ztx.statusUpdates().collect { + println("status: $it") + when(it) { + ZitiContext.Status.Loading, ZitiContext.Status.Authenticating -> {} + ZitiContext.Status.Active -> { + println("verification success!") + cancel() + } + is ZitiContext.Status.NotAuthorized -> { + cancel("verification failed!", it.ex) + } + else -> cancel("unexpected status") + } + } + } + + runBlocking { + j.join() + + if (showCodes || newCodes) { + print("""enter OTP to ${if (newCodes) "generate" else "show"} recovery codes: """) + val code = readLine() + val recCodes = ztx.getMFARecoveryCodes(code!!, newCodes) + for (rc in recCodes) { + println(rc) + } + } + + ztx.destroy() + } + + } + } + + private class mfa: CliktCommand(help = "Enroll identity in MFA") { + val idFile by option(help = "identity configuration file.").file().required().validate { + it.exists() + } + + override fun run() { + val ztx = Ziti.newContext(idFile, charArrayOf()) + + val j = GlobalScope.launch { + ztx.statusUpdates().takeWhile { it != ZitiContext.Status.Active }.collectLatest { println(it) } + val mfa = ztx.enrollMFA() + println(mfa) + + print("Enter OTP code: ") + val code = readLine() + ztx.verifyMFA(code!!.trim()) + } + runBlocking { j.join() } + + ztx.destroy() + } + + } + + private class Cli : CliktCommand(name = "ziti-enroller") { + + init { + subcommands(enroll(), verify(), mfa()) + } + + override fun run() { + } + } + @JvmStatic fun main(args: Array) = Cli().main(args) } \ No newline at end of file diff --git a/ziti/src/main/kotlin/org/openziti/Exceptions.kt b/ziti/src/main/kotlin/org/openziti/Exceptions.kt index aeb3d5ff..9f9bd7f7 100644 --- a/ziti/src/main/kotlin/org/openziti/Exceptions.kt +++ b/ziti/src/main/kotlin/org/openziti/Exceptions.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 NetFoundry, Inc. + * Copyright (c) 2018-2021 NetFoundry, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,8 @@ private val errorMap = mapOf( "REQUIRES_CERT_AUTH" to Errors.NotAuthorized, "UNAUTHORIZED" to Errors.NotAuthorized, "INVALID_AUTH" to Errors.NotAuthorized, - "INVALID_POSTURE" to Errors.InsufficientSecurity + "INVALID_POSTURE" to Errors.InsufficientSecurity, + "MFA_INVALID_TOKEN" to Errors.InsufficientSecurity ) fun getZitiError(err: String): Errors = errorMap.getOrElse(err) { Errors.WTF(err) } diff --git a/ziti/src/main/kotlin/org/openziti/Ziti.kt b/ziti/src/main/kotlin/org/openziti/Ziti.kt index cc7740bb..d76712ee 100644 --- a/ziti/src/main/kotlin/org/openziti/Ziti.kt +++ b/ziti/src/main/kotlin/org/openziti/Ziti.kt @@ -17,6 +17,7 @@ package org.openziti import kotlinx.coroutines.flow.Flow +import org.openziti.api.MFAType import org.openziti.api.Service import org.openziti.impl.ZitiImpl import org.openziti.net.ZitiSocketFactory @@ -26,6 +27,7 @@ import org.openziti.net.nio.AsyncTLSSocketFactory import java.io.File import java.net.SocketAddress import java.security.KeyStore +import java.util.concurrent.CompletionStage import javax.net.SocketFactory import javax.net.ssl.SSLSocketFactory @@ -34,6 +36,11 @@ import javax.net.ssl.SSLSocketFactory */ object Ziti { + @FunctionalInterface + interface AuthHandler { + fun getCode(ztx: ZitiContext, mfaType: MFAType, provider: String): CompletionStage + } + /** * Load Ziti identity from the file. * The following formats of ziti identity files are supported: @@ -44,7 +51,8 @@ object Ziti { * @param pwd password to access the file (only needed for .jks or .pfx/.p12 if they are protected by password) */ @JvmStatic - fun newContext(idFile: File, pwd: CharArray): ZitiContext = ZitiImpl.loadContext(idFile, pwd, null) + @JvmOverloads + fun newContext(idFile: File, pwd: CharArray, auth: AuthHandler? = null): ZitiContext = ZitiImpl.loadContext(idFile, pwd, null, auth) /** * Load Ziti identity from the file. @@ -53,16 +61,19 @@ object Ziti { * @param pwd password to access the file (only needed for .jks or .pfx/.p12 if they are protected by password) */ @JvmStatic - fun newContext(fname: String, pwd: CharArray): ZitiContext = newContext(File(fname), pwd) + @JvmOverloads + fun newContext(fname: String, pwd: CharArray, auth: AuthHandler? = null): ZitiContext = newContext(File(fname), pwd, auth) @JvmStatic fun removeContext(ctx: ZitiContext) = ZitiImpl.removeContext(ctx) @JvmStatic - fun init(fname: String, pwd: CharArray, seamless: Boolean) = ZitiImpl.init(File(fname), pwd, seamless) + @JvmOverloads + fun init(fname: String, pwd: CharArray, seamless: Boolean, auth: AuthHandler? = null) = ZitiImpl.init(File(fname), pwd, seamless, auth) @JvmStatic - fun init(ks: KeyStore, seamless: Boolean) = ZitiImpl.init(ks, seamless) + @JvmOverloads + fun init(ks: KeyStore, seamless: Boolean, auth: AuthHandler? = null) = ZitiImpl.init(ks, seamless, auth) @JvmStatic fun enroll(ks: KeyStore, jwt: ByteArray, name: String): ZitiContext = ZitiImpl.enroll(ks, jwt, name) diff --git a/ziti/src/main/kotlin/org/openziti/ZitiContext.kt b/ziti/src/main/kotlin/org/openziti/ZitiContext.kt index ec5f1a11..4c04393b 100644 --- a/ziti/src/main/kotlin/org/openziti/ZitiContext.kt +++ b/ziti/src/main/kotlin/org/openziti/ZitiContext.kt @@ -16,7 +16,11 @@ package org.openziti +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.future.asCompletableFuture +import org.openziti.api.MFAEnrollment import org.openziti.api.Service import org.openziti.api.ServiceTerminator import org.openziti.identity.Identity @@ -24,6 +28,7 @@ import java.net.InetSocketAddress import java.net.Socket import java.nio.channels.AsynchronousServerSocketChannel import java.nio.channels.AsynchronousSocketChannel +import java.util.concurrent.Future import org.openziti.api.Identity as ApiIdentity /** @@ -44,6 +49,7 @@ interface ZitiContext: Identity { sealed class Status { object Loading: Status() + object Authenticating: Status() object Active: Status() object Disabled: Status() class NotAuthorized(val ex: Throwable): Status() @@ -112,4 +118,19 @@ interface ZitiContext: Identity { fun destroy() + suspend fun enrollMFA(): MFAEnrollment + fun enrollMFAAsync() = GlobalScope.async { + enrollMFA() + }.asCompletableFuture() + + suspend fun verifyMFA(code: String) + fun verifyMFAAsync(code: String) = GlobalScope.async { verifyMFA(code) }.asCompletableFuture() + + suspend fun removeMFA(code: String) + fun removeMFAAsync(code: String) = + GlobalScope.async { removeMFA(code) }.asCompletableFuture() + + suspend fun getMFARecoveryCodes(code: String, newCodes: Boolean): Array + fun getMFARecoveryCodesAsync(code: String, newCodes: Boolean) = + GlobalScope.async { getMFARecoveryCodes(code, newCodes) }.asCompletableFuture() } \ No newline at end of file diff --git a/ziti/src/main/kotlin/org/openziti/api/Controller.kt b/ziti/src/main/kotlin/org/openziti/api/Controller.kt index be6504fa..d72b069e 100644 --- a/ziti/src/main/kotlin/org/openziti/api/Controller.kt +++ b/ziti/src/main/kotlin/org/openziti/api/Controller.kt @@ -28,7 +28,6 @@ import okhttp3.OkHttpClient import okhttp3.ResponseBody import okhttp3.logging.HttpLoggingInterceptor import org.openziti.Errors -import org.openziti.ZitiContext import org.openziti.ZitiException import org.openziti.getZitiError import org.openziti.impl.ZitiImpl @@ -46,7 +45,6 @@ import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.* import java.io.IOException import java.net.URL -import java.time.Instant import java.util.* import javax.net.ssl.SSLContext import javax.net.ssl.X509TrustManager @@ -71,6 +69,27 @@ internal class Controller(endpoint: URL, sslContext: SSLContext, trustManager: X @DELETE("current-api-session") fun logout(): Deferred + @GET("/current-identity/mfa") + fun getMFA(): Deferred> + + @POST("/current-identity/mfa") + fun postMFA(): Deferred> + + @DELETE("/current-identity/mfa") + fun removeMFA(@Header("mfa-validation-code") code: String): Deferred> + + @POST("/authenticate/mfa") + fun authMFA(@Body code: MFACode): Deferred> + + @POST("/current-identity/mfa/verify") + fun verifyMFA(@Body code: MFACode): Deferred> + + @GET("/current-identity/mfa/recovery-codes") + fun getMFACodes(@Header("mfa-validation-code") code: String): Deferred> + + @POST("/current-identity/mfa/recovery-codes") + fun newMFACodes(@Body code: MFACode): Deferred> + @GET("/current-api-session/service-updates") fun getServiceUpdates(): Deferred> @@ -216,6 +235,40 @@ internal class Controller(endpoint: URL, sslContext: SSLContext, trustManager: X offset -> api.getServiceTerminators(s.id, offset = offset) } + internal suspend fun postMFA() = api.postMFA().await().data + + internal suspend fun getMFAEnrollment(): MFAEnrollment? = + runCatching { api.getMFA().await().data } + .onFailure { + if (it !is HttpException) throw it + if (it.code() != 404) throw it + }.getOrNull() + + internal suspend fun verifyMFA(code: String) { + runCatching { + api.verifyMFA(MFACode(code)).await() + }.getOrElse { convertError(it) } + } + + internal suspend fun authMFA(code: String) { + runCatching { api.authMFA(MFACode(code)).await() } + .getOrElse { convertError(it) } + } + + internal suspend fun removeMFA(code: String) { + runCatching { api.removeMFA(code).await() } + .getOrElse { convertError(it) } + } + + internal suspend fun getMFARecoveryCodes(code: String, newCodes: Boolean): Array { + if (newCodes) { + api.newMFACodes(MFACode(code)).await() + } + + val codes = api.getMFACodes(code) + return codes.await().data?.recoveryCodes ?: emptyArray() + } + private fun pagingRequest(req: (offset: Int) -> Deferred>>) = flow { var offset = 0 @@ -242,7 +295,6 @@ internal class Controller(endpoint: URL, sslContext: SSLContext, trustManager: X } private fun convertError(t: Throwable): Nothing { - e("error: ${t.localizedMessage}") val errCode = when (t) { is HttpException -> getZitiError(getError(t.response())) is IOException -> Errors.ControllerUnavailable diff --git a/ziti/src/main/kotlin/org/openziti/api/mfa.kt b/ziti/src/main/kotlin/org/openziti/api/mfa.kt new file mode 100644 index 00000000..f43f8d9b --- /dev/null +++ b/ziti/src/main/kotlin/org/openziti/api/mfa.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-2021 NetFoundry, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openziti.api + +enum class MFAType { + MFA, + CUSTOM +} + +data class AuthQueryMFA ( + val typeId: MFAType?, + val provider: String, + val httpMethod: String, + val httpUrl: String, + val minLength: Int, + val maxLength: Int, + val format: String +) + +data class MFAEnrollment ( + val isVerified: Boolean, + val recoveryCodes: Array, + val provisioningUrl: String +) + +data class MFACode ( + val code: String +) + +data class MFARecoveryCodes ( + val recoveryCodes: Array +) \ No newline at end of file diff --git a/ziti/src/main/kotlin/org/openziti/api/types.kt b/ziti/src/main/kotlin/org/openziti/api/types.kt index eb843ec4..ef696e6e 100644 --- a/ziti/src/main/kotlin/org/openziti/api/types.kt +++ b/ziti/src/main/kotlin/org/openziti/api/types.kt @@ -37,7 +37,8 @@ enum class PostureQueryType { OS, MAC, DOMAIN, - PROCESS + PROCESS, + MFA } enum class InterceptProtocol { @@ -66,7 +67,8 @@ internal class ApiSession( val identity: Identity, val updatedAt: Date, val expiresAt: Date, - val expirationSeconds: Int + val expirationSeconds: Int, + val authQueries: Array? ) internal class ServiceUpdates(val lastChangeAt: Date) @@ -129,7 +131,7 @@ data class PostureQueryProcess ( ) data class PostureQuery ( - val queryType: PostureQueryType, + val queryType: PostureQueryType?, val id: String, val isPassing: Boolean, val process: PostureQueryProcess? diff --git a/ziti/src/main/kotlin/org/openziti/impl/ZitiContextImpl.kt b/ziti/src/main/kotlin/org/openziti/impl/ZitiContextImpl.kt index b1f7e3e1..145fd619 100644 --- a/ziti/src/main/kotlin/org/openziti/impl/ZitiContextImpl.kt +++ b/ziti/src/main/kotlin/org/openziti/impl/ZitiContextImpl.kt @@ -19,6 +19,7 @@ package org.openziti.impl import kotlinx.coroutines.* import kotlinx.coroutines.channels.produce import kotlinx.coroutines.flow.* +import kotlinx.coroutines.future.await import kotlinx.coroutines.selects.select import kotlinx.coroutines.selects.whileSelect import org.openziti.* @@ -51,7 +52,7 @@ import org.openziti.api.Identity as ApiIdentity * Object maintaining current Ziti session. * */ -internal class ZitiContextImpl(internal val id: Identity, enabled: Boolean) : ZitiContext, Identity by id, +internal class ZitiContextImpl(internal val id: Identity, enabled: Boolean, internal val auth: Ziti.AuthHandler?) : ZitiContext, Identity by id, CoroutineScope, Logged by ZitiLog() { private var _enabled: Boolean by Delegates.observable(enabled) { _, _, isEnabled -> @@ -189,6 +190,30 @@ internal class ZitiContextImpl(internal val id: Identity, enabled: Boolean) : Zi throw it } + val mfa = apiSession?.authQueries?.find { it.typeId == MFAType.MFA && it.provider == "ziti" } + mfa?.let { + if (auth == null) { + val ex = ZitiException( + Errors.NotAuthorized, + IllegalStateException("identity requires MFA, but callback was not provided by the app") + ) + updateStatus(ZitiContext.Status.NotAuthorized(ex)) + throw ex + } + } + + if (mfa != null) { + updateStatus(ZitiContext.Status.Authenticating) + runCatching { + val codeF = auth!!.getCode(this, mfa.typeId!!, mfa.provider) + val code = codeF.await() + controller.authMFA(code) + }.getOrElse { + updateStatus(ZitiContext.Status.NotAuthorized(it)) + throw it + } + } + updateStatus(ZitiContext.Status.Active) val apiSessionUpdate = maintainApiSession() @@ -517,4 +542,25 @@ internal class ZitiContextImpl(internal val id: Identity, enabled: Boolean) : Zi statusCh.takeWhile { it != ZitiContext.Status.Active }.collect() } } + + override suspend fun enrollMFA(): MFAEnrollment { + + val mfaEnrollment = runCatching { controller.getMFAEnrollment() }.getOrThrow() + if (mfaEnrollment != null) return mfaEnrollment + + controller.postMFA() + return controller.getMFAEnrollment()!! + } + + override suspend fun verifyMFA(code: String) { + return controller.verifyMFA(code) + } + + override suspend fun removeMFA(code: String) { + controller.removeMFA(code) + } + + override suspend fun getMFARecoveryCodes(code: String, newCodes: Boolean): Array { + return controller.getMFARecoveryCodes(code, newCodes) + } } \ No newline at end of file diff --git a/ziti/src/main/kotlin/org/openziti/impl/ZitiImpl.kt b/ziti/src/main/kotlin/org/openziti/impl/ZitiImpl.kt index d6a321d3..19ffd668 100644 --- a/ziti/src/main/kotlin/org/openziti/impl/ZitiImpl.kt +++ b/ziti/src/main/kotlin/org/openziti/impl/ZitiImpl.kt @@ -42,6 +42,7 @@ internal object ZitiImpl : Logged by ZitiLog() { internal val contexts = mutableListOf() internal var appId = "" internal var appVersion = "" + internal var authHandler: Ziti.AuthHandler? = null internal val serviceEvents = MutableSharedFlow>() @@ -58,10 +59,10 @@ internal object ZitiImpl : Logged by ZitiLog() { i("ZitiSDK version ${Version.version} @${Version.revision}(${Version.branch})") } - internal fun loadContext(ks: KeyStore, alias: String?): ZitiContextImpl { + internal fun loadContext(ks: KeyStore, alias: String?, authHandler: Ziti.AuthHandler?): ZitiContextImpl { val idName = alias ?: findIdentityAlias(ks) val id = KeyStoreIdentity(ks, idName) - return ZitiContextImpl(id, true).also { ctx -> + return ZitiContextImpl(id, true, authHandler).also { ctx -> contexts.add(ctx) GlobalScope.launch { ctx.serviceUpdates().collect { @@ -71,17 +72,17 @@ internal object ZitiImpl : Logged by ZitiLog() { } } - internal fun loadContext(idFile: File, pwd: CharArray, alias: String?): ZitiContextImpl { + internal fun loadContext(idFile: File, pwd: CharArray, alias: String?, auth: Ziti.AuthHandler?): ZitiContextImpl { val ks = loadKeystore(idFile, pwd) - return loadContext(ks, alias) + return loadContext(ks, alias, auth) } - fun init(file: File, pwd: CharArray, seamless: Boolean): Unit { + fun init(file: File, pwd: CharArray, seamless: Boolean, authHandler: Ziti.AuthHandler?): Unit { if (seamless) { initInternalNetworking() } - val ctx = loadContext(file, pwd, null) + val ctx = loadContext(file, pwd, null, authHandler) ctx.checkServicesLoaded() } @@ -92,14 +93,15 @@ internal object ZitiImpl : Logged by ZitiLog() { } } - fun init(ks: KeyStore, seamless: Boolean): List { + fun init(ks: KeyStore, seamless: Boolean, authHandler: Ziti.AuthHandler?): List { + this.authHandler = authHandler if (seamless) { initInternalNetworking() } for (a in ks.aliases()) { if (isZitiIdentity(ks, a)) { - loadContext(ks, a) + loadContext(ks, a, authHandler) } } @@ -126,7 +128,7 @@ internal object ZitiImpl : Logged by ZitiLog() { val enroller = Enroller.fromJWT(String(jwt)) val alias = enroller.enroll(null, ks, name) - return loadContext(ks, alias) + return loadContext(ks, alias, authHandler) } fun getServiceFor(host: String, port: Int): Pair? = contexts.map { c -> diff --git a/ziti/src/main/kotlin/org/openziti/posture/DefaultPostureService.kt b/ziti/src/main/kotlin/org/openziti/posture/DefaultPostureService.kt index 1e0dcd9c..d6f7adb6 100644 --- a/ziti/src/main/kotlin/org/openziti/posture/DefaultPostureService.kt +++ b/ziti/src/main/kotlin/org/openziti/posture/DefaultPostureService.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 NetFoundry, Inc. + * Copyright (c) 2018-2021 NetFoundry, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,11 @@ package org.openziti.posture import org.openziti.api.PostureQuery import org.openziti.api.PostureQueryType import org.openziti.api.PostureResponse +import org.openziti.util.Logged +import org.openziti.util.ZitiLog import java.net.NetworkInterface -internal class DefaultPostureService: PostureService { +internal class DefaultPostureService: PostureService, Logged by ZitiLog() { internal val queries = mutableMapOf() object Provider: PostureServiceProvider { @@ -56,7 +58,10 @@ internal class DefaultPostureService: PostureService { return when(q.queryType) { PostureQueryType.OS -> PostureResponse.OS(q.id, osName, osVersion, "") PostureQueryType.MAC -> PostureResponse.MAC(q.id, macs) - else -> null + else -> { + w{"unsupported posture type: $q"} + null + } } } } \ No newline at end of file