ktor sslConnector serve/read 来自 jar p12 pkcs12 jks 密钥库和 mtls 相互 ssl 连接

ktor sslConnector serve/read from jar p12 pkcs12 jks keystore and mtls mutual ssl connection

我已经弄清楚如何使用 embeddedServer Jetty 启动 ktor 并使用我自己的签名证书(由我自己的自签名 rootCA 证书签名)服务 https/ssl/tls。

现在 ktor sslConnector 需要 keyStorePath 作为 File,但我更愿意从最终 fat jar 中的 the/a 文件提供密钥库(主要是为了能够运行 它在 kubernetes 集群中)

有没有办法告诉 ktor take/read 一个嵌入到它的 jar 文件中的资源作为 keyStore?

    // openssl pkcs12 -export -nodes -passout pass:${keystorePW} -in "domain.cert" -inkey "domain.key" -certfile "intermediateAndRootCAchain.ca" -name "aliasName" -out "webserverKeystore.p12"
    val keystore: KeyStore = KeyStore.getInstance(keystoreFile, keystorePW.toCharArray())

    @Suppress("UNUSED_PARAMETER")
    fun doIt(args: Array<String>) {
        val server = embeddedServer(Jetty, applicationEngineEnvironment {
            module {
                configureRouting()
                configureHTTP(sslPort)
                configureSerialization()
            }

            connector {
                this.host = host // redirected to https
                this.port = port // redirected to sslPort
            }

            sslConnector(keystore,
                keyAlias = certAlias, // alias name inside keystore: keytool -v -list -keystore certs/keystore.jks
                keyStorePassword = { keystorePW.toCharArray() },
                privateKeyPassword = { keystorePW.toCharArray() } // somehow this is the same as keystorePW if using openssl pkcs12 -export from above
            ) {
                this.port = sslPort
                keyStorePath = keystoreFile
            }
        })

        server.start(wait = true)
    }

有没有办法告诉 ktor take/read 一个嵌入到它的 jar 文件中的资源作为 keyStore?

(还想知道为什么 sslConnector 仍然需要密钥库作为 File 如果它已经将整个密钥库作为第一个参数,但这可能与正在使用的实际 Web 容器无关)

第二个问题:是否可以启用mutual tls,以便ktor服务器在客户端无法呈现时拒绝连接有效证书?如果是,我该如何配置?

对于 NettyJetty 引擎,实际上使用了作为 sslConnector 的第一个参数传递的 keystorekeyStorePath 属性 仅用于 Tomcat 引擎。

不幸的是,无法在 Ktor 中配置相互 TLS 身份验证,但作为一种解决方法,您可以通过向底层 Jetty 服务器添加连接器来手动完成。 This article 可能对您有用。这是一个不完整的例子:

embeddedServer(
    Jetty, 
    applicationEngineEnvironment {
        module {
            // ...
        }
    }
) {
    configureServer = {
        val factory = SslConnectionFactory(
            SslContextFactory.Server().apply {
                // keyStore = ...
                // setKeyManagerPassword(...)
                // setKeyStorePassword(...)
                needClientAuth = true
            },
            HttpVersion.HTTP_1_1.asString()
        )

        val connector = ServerConnector(this, factory).apply {
            // host = ...
            // port = ...
        }

        addConnector(connector)
    }
}

这是我的最终使用代码:

        val (theTrustStore, theKeyStore) = createTrustStoreAndKeyStore()

        val server = embeddedServer(Jetty, applicationEngineEnvironment {
            module {
                configureRouting()
                configureHTTP(theSslPort)
                configureSerialization()
            }

            connector {
                this.host = theBindHost // redirected to https
                this.port = thePort // redirected to sslPort
            }

            // // without mTLS (m = mutual)
            // sslConnector(theKeyStore,
            //     keyAlias = theHost, // alias name inside keystore: keytool -v -list -keystore certs/keystore.jks
            //     keyStorePassword = { keystorePW.toCharArray() },
            //     privateKeyPassword = { privateKeyPW.toCharArray() } // somehow this is the same as keystorePW if using openssl pkcs12 -export from above
            // ) {
            //     this.host = theBindHost
            //     this.port = theSslPort
            //     keyStorePath = null // only used by tomcat engine
            // }
        }) {
            configureServer = {
                val factory = SslConnectionFactory(
                    SslContextFactory.Server().apply {
                        keyStore = theKeyStore
                        trustStore = theTrustStore
                        // setKeyManagerPassword(...)
                        // setKeyStorePassword(...)
                        needClientAuth = true
                    },
                    HttpVersion.HTTP_1_1.asString()
                )

                val httpConfig = HttpConfiguration()
                httpConfig.secureScheme = "https"
                httpConfig.securePort = theSslPort
                // SSL HTTP Configuration
                val httpsConfig = HttpConfiguration(httpConfig)
                httpsConfig.addCustomizer(SecureRequestCustomizer()) // so that servlets can see the encryption details


                val connector = ServerConnector(
                    this,
                    factory,
                    HttpConnectionFactory(httpsConfig)
                ).apply {
                    host = theBindHost
                    port = theSslPort
                }

                addConnector(connector)
            }
        }

        server.start(wait = true)
    }