Skip to content

Commit 581e8a2

Browse files
committed
Merge branch 'main' into eap
2 parents 6beb03a + f140b7e commit 581e8a2

23 files changed

+728
-59
lines changed

.github/workflows/build.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- windows-latest
2323
runs-on: ${{ matrix.platform }}
2424
steps:
25-
- uses: actions/checkout@v4.1.0
25+
- uses: actions/checkout@v4.1.1
2626

2727
- uses: actions/setup-java@v3
2828
with:
@@ -55,7 +55,7 @@ jobs:
5555
steps:
5656
# Check out current repository
5757
- name: Fetch Sources
58-
uses: actions/checkout@v4.1.0
58+
uses: actions/checkout@v4.1.1
5959

6060
# Setup Java 11 environment for the next steps
6161
- name: Setup Java
@@ -110,7 +110,7 @@ jobs:
110110

111111
# Run Qodana inspections
112112
- name: Qodana - Code Inspection
113-
uses: JetBrains/qodana-action@v2023.2.6
113+
uses: JetBrains/qodana-action@v2023.2.8
114114

115115
# Prepare plugin archive content for creating artifact
116116
- name: Prepare Plugin Artifact
@@ -139,7 +139,7 @@ jobs:
139139

140140
# Check out current repository
141141
- name: Fetch Sources
142-
uses: actions/checkout@v4.1.0
142+
uses: actions/checkout@v4.1.1
143143

144144
# Remove old release drafts by using the curl request for the available releases with draft flag
145145
- name: Remove Old Release Drafts

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515

1616
# Check out current repository
1717
- name: Fetch Sources
18-
uses: actions/checkout@v4.1.0
18+
uses: actions/checkout@v4.1.1
1919
with:
2020
ref: ${{ github.event.release.tag_name }}
2121

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
## Unreleased
66

7+
## 2.9.1 - 2023-11-06
8+
9+
### Fixed
10+
11+
- Set the `CODER_HEADER_COMMAND` environment variable when executing the CLI with the setting value.
12+
13+
## 2.9.0 - 2023-10-27
14+
715
### Added
816

917
- Configuration options for mTLS.

build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ version = properties("pluginVersion")
2424
dependencies {
2525
implementation("com.squareup.retrofit2:retrofit:2.9.0")
2626
// define a BOM and its version
27-
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.11.0"))
27+
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
2828
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
2929
implementation("com.squareup.okhttp3:okhttp")
3030
implementation("com.squareup.okhttp3:logging-interceptor")

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
pluginGroup=com.coder.gateway
44
pluginName=coder-gateway
55
# SemVer format -> https://semver.org
6-
pluginVersion=2.9.0-eap.0
6+
pluginVersion=2.9.1-eap.0
77
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
88
# for insight into build numbers and IntelliJ Platform versions.
99
pluginSinceBuild=233.6745

src/main/kotlin/com/coder/gateway/CoderGatewayConnectionProvider.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
140140
if (token == null) { // User aborted.
141141
throw IllegalArgumentException("Unable to connect to $deploymentURL, $TOKEN is missing")
142142
}
143-
val client = CoderRestClient(deploymentURL, token.first,null, settings)
143+
val client = CoderRestClient(deploymentURL, token.first, null, settings)
144144
return try {
145145
Pair(client, client.me().username)
146146
} catch (ex: AuthenticationResponseException) {

src/main/kotlin/com/coder/gateway/sdk/CoderCLIManager.kt

+7
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ class CoderCLIManager @JvmOverloads constructor(
101101
fun downloadCLI(): Boolean {
102102
val etag = getBinaryETag()
103103
val conn = remoteBinaryURL.openConnection() as HttpURLConnection
104+
if (settings.headerCommand.isNotBlank()) {
105+
val headersFromHeaderCommand = CoderRestClient.getHeaders(deploymentURL, settings.headerCommand)
106+
for ((key, value) in headersFromHeaderCommand) {
107+
conn.setRequestProperty(key, value)
108+
}
109+
}
104110
if (etag != null) {
105111
logger.info("Found existing binary at $localBinaryPath; calculated hash as $etag")
106112
conn.setRequestProperty("If-None-Match", "\"$etag\"")
@@ -364,6 +370,7 @@ class CoderCLIManager @JvmOverloads constructor(
364370
private fun exec(vararg args: String): String {
365371
val stdout = ProcessExecutor()
366372
.command(localBinaryPath.toString(), *args)
373+
.environment("CODER_HEADER_COMMAND", settings.headerCommand)
367374
.exitValues(0)
368375
.readOutput(true)
369376
.execute()

src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt

+48-46
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.google.gson.Gson
1919
import com.google.gson.GsonBuilder
2020
import com.intellij.ide.plugins.PluginManagerCore
2121
import com.intellij.openapi.components.Service
22+
import com.intellij.openapi.diagnostic.Logger
2223
import com.intellij.openapi.extensions.PluginId
2324
import com.intellij.openapi.util.SystemInfo
2425
import okhttp3.OkHttpClient
@@ -36,7 +37,6 @@ import java.net.URL
3637
import java.nio.file.Path
3738
import java.security.KeyFactory
3839
import java.security.KeyStore
39-
import java.security.PrivateKey
4040
import java.security.cert.CertificateException
4141
import java.security.cert.CertificateFactory
4242
import java.security.cert.X509Certificate
@@ -47,6 +47,7 @@ import java.util.Base64
4747
import java.util.Locale
4848
import java.util.UUID
4949
import javax.net.ssl.HostnameVerifier
50+
import javax.net.ssl.KeyManager
5051
import javax.net.ssl.KeyManagerFactory
5152
import javax.net.ssl.SNIHostName
5253
import javax.net.ssl.SSLContext
@@ -251,53 +252,56 @@ class CoderRestClient(
251252
}
252253
}
253254

254-
fun coderSocketFactory(settings: CoderSettingsState) : SSLSocketFactory {
255-
if (settings.tlsCertPath.isBlank() || settings.tlsKeyPath.isBlank()) {
256-
return SSLSocketFactory.getDefault() as SSLSocketFactory
257-
}
255+
fun SSLContextFromPEMs(certPath: String, keyPath: String, caPath: String) : SSLContext {
256+
var km: Array<KeyManager>? = null
257+
if (certPath.isNotBlank() && keyPath.isNotBlank()) {
258+
val certificateFactory = CertificateFactory.getInstance("X.509")
259+
val certInputStream = FileInputStream(expandPath(certPath))
260+
val certChain = certificateFactory.generateCertificates(certInputStream)
261+
certInputStream.close()
262+
263+
// ideally we would use something like PemReader from BouncyCastle, but
264+
// BC is used by the IDE. This makes using BC very impractical since
265+
// type casting will mismatch due to the different class loaders.
266+
val privateKeyPem = File(expandPath(keyPath)).readText()
267+
val start: Int = privateKeyPem.indexOf("-----BEGIN PRIVATE KEY-----")
268+
val end: Int = privateKeyPem.indexOf("-----END PRIVATE KEY-----", start)
269+
val pemBytes: ByteArray = Base64.getDecoder().decode(
270+
privateKeyPem.substring(start + "-----BEGIN PRIVATE KEY-----".length, end)
271+
.replace("\\s+".toRegex(), "")
272+
)
273+
274+
val privateKey = try {
275+
val kf = KeyFactory.getInstance("RSA")
276+
val keySpec = PKCS8EncodedKeySpec(pemBytes)
277+
kf.generatePrivate(keySpec)
278+
} catch (e: InvalidKeySpecException) {
279+
val kf = KeyFactory.getInstance("EC")
280+
val keySpec = PKCS8EncodedKeySpec(pemBytes)
281+
kf.generatePrivate(keySpec)
282+
}
258283

259-
val certificateFactory = CertificateFactory.getInstance("X.509")
260-
val certInputStream = FileInputStream(expandPath(settings.tlsCertPath))
261-
val certChain = certificateFactory.generateCertificates(certInputStream)
262-
certInputStream.close()
263-
264-
// ideally we would use something like PemReader from BouncyCastle, but
265-
// BC is used by the IDE. This makes using BC very impractical since
266-
// type casting will mismatch due to the different class loaders.
267-
val privateKeyPem = File(expandPath(settings.tlsKeyPath)).readText()
268-
val start: Int = privateKeyPem.indexOf("-----BEGIN PRIVATE KEY-----")
269-
val end: Int = privateKeyPem.indexOf("-----END PRIVATE KEY-----", start)
270-
val pemBytes: ByteArray = Base64.getDecoder().decode(
271-
privateKeyPem.substring(start + "-----BEGIN PRIVATE KEY-----".length, end)
272-
.replace("\\s+".toRegex(), "")
273-
)
274-
275-
var privateKey : PrivateKey
276-
try {
277-
val kf = KeyFactory.getInstance("RSA")
278-
val keySpec = PKCS8EncodedKeySpec(pemBytes)
279-
privateKey = kf.generatePrivate(keySpec)
280-
} catch (e: InvalidKeySpecException) {
281-
val kf = KeyFactory.getInstance("EC")
282-
val keySpec = PKCS8EncodedKeySpec(pemBytes)
283-
privateKey = kf.generatePrivate(keySpec)
284-
}
285-
286-
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
287-
keyStore.load(null)
288-
certChain.withIndex().forEach {
289-
keyStore.setCertificateEntry("cert${it.index}", it.value as X509Certificate)
290-
}
291-
keyStore.setKeyEntry("key", privateKey, null, certChain.toTypedArray())
284+
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
285+
keyStore.load(null)
286+
certChain.withIndex().forEach {
287+
keyStore.setCertificateEntry("cert${it.index}", it.value as X509Certificate)
288+
}
289+
keyStore.setKeyEntry("key", privateKey, null, certChain.toTypedArray())
292290

293-
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
294-
keyManagerFactory.init(keyStore, null)
291+
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
292+
keyManagerFactory.init(keyStore, null)
293+
km = keyManagerFactory.keyManagers
294+
}
295295

296296
val sslContext = SSLContext.getInstance("TLS")
297297

298-
val trustManagers = coderTrustManagers(settings.tlsCAPath)
299-
sslContext.init(keyManagerFactory.keyManagers, trustManagers, null)
298+
val trustManagers = coderTrustManagers(caPath)
299+
sslContext.init(km, trustManagers, null)
300+
return sslContext
301+
}
300302

303+
fun coderSocketFactory(settings: CoderSettingsState) : SSLSocketFactory {
304+
val sslContext = SSLContextFromPEMs(settings.tlsCertPath, settings.tlsKeyPath, settings.tlsCAPath)
301305
if (settings.tlsAlternateHostname.isBlank()) {
302306
return sslContext.socketFactory
303307
}
@@ -393,12 +397,11 @@ class AlternateNameSSLSocketFactory(private val delegate: SSLSocketFactory, priv
393397
}
394398

395399
class CoderHostnameVerifier(private val alternateName: String) : HostnameVerifier {
400+
val logger = Logger.getInstance(CoderRestClientService::class.java.simpleName)
396401
override fun verify(host: String, session: SSLSession): Boolean {
397402
if (alternateName.isEmpty()) {
398-
println("using default hostname verifier, alternateName is empty")
399403
return OkHostnameVerifier.verify(host, session)
400404
}
401-
println("Looking for alternate hostname: $alternateName")
402405
val certs = session.peerCertificates ?: return false
403406
for (cert in certs) {
404407
if (cert !is X509Certificate) {
@@ -411,13 +414,12 @@ class CoderHostnameVerifier(private val alternateName: String) : HostnameVerifie
411414
continue
412415
}
413416
val hostname = entry[1] as String
414-
println("Found cert hostname: $hostname")
417+
logger.debug("Found cert hostname: $hostname")
415418
if (hostname.lowercase(Locale.getDefault()) == alternateName) {
416419
return true
417420
}
418421
}
419422
}
420-
println("No matching hostname found")
421423
return false
422424
}
423425
}

src/main/resources/messages/CoderGatewayBundle.properties

+4-4
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,20 @@ gateway.connector.settings.header-command.comment=An external command that \
9393
outputs additional HTTP headers added to all requests. The command must \
9494
output each header as `key=value` on its own line. The following \
9595
environment variables will be available to the process: CODER_URL.
96-
gateway.connector.settings.tls-cert-path.title=Cert Path:
96+
gateway.connector.settings.tls-cert-path.title=Cert path:
9797
gateway.connector.settings.tls-cert-path.comment=Optionally set this to \
9898
the path of a certificate to use for TLS connections. The certificate \
9999
should be in X.509 PEM format.
100-
gateway.connector.settings.tls-key-path.title=Key Path:
100+
gateway.connector.settings.tls-key-path.title=Key path:
101101
gateway.connector.settings.tls-key-path.comment=Optionally set this to \
102102
the path of the private key that corresponds to the above cert path to use \
103103
for TLS connections. The key should be in X.509 PEM format.
104-
gateway.connector.settings.tls-ca-path.title=CA Path:
104+
gateway.connector.settings.tls-ca-path.title=CA path:
105105
gateway.connector.settings.tls-ca-path.comment=Optionally set this to \
106106
the path of a file containing certificates for an alternate certificate \
107107
authority used to verify TLS certs returned by the Coder service. \
108108
The file should be in X.509 PEM format.
109-
gateway.connector.settings.tls-alt-name.title=Alt Hostname:
109+
gateway.connector.settings.tls-alt-name.title=Alt hostname:
110110
gateway.connector.settings.tls-alt-name.comment=Optionally set this to \
111111
an alternate hostname used for verifying TLS connections. This is useful \
112112
when the hostname used to connect to the Coder service does not match the \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICvjCCAaagAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJVEVT
3+
VC1yb290MCAXDTIzMTAzMDAyMzY0M1oYDzIxMjMxMDA2MDIzNjQzWjAcMRowGAYD
4+
VQQDDBFURVNULWludGVybWVkaWF0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
5+
AQoCggEBAMGD9oILmMRcplGkcNdSZMsBR7C2yoPtL9iRp3V2BKpiRZvQuXHSQsdc
6+
S0Tpk6vnIWQTLkCjVRawL9BoOzwK3FZQti9iXRMnHuzl0gQGZGiHJZ2P/efWaVvn
7+
cmH3Cu2oNCVePhgYAMOiipYGQPcjnQ2kUvMLldZ9+WC+EcaD+FA/kaccPX+kOxQg
8+
qQ0MnPQFfno0F8gylOac+ouKOsXya+jlctgK3dxC73/I+Cdq8xrOJ8lXOYxggleB
9+
ZRXNWWUhrzomn4rUP9wNBrQzFCGcqIS+QjlACjlyn0gPU//ZGVRZ8gZXoI8pDYuB
10+
lRyWpt970/ZPFuiyfiasAAAc8gJ3C7cCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zAN
11+
BgkqhkiG9w0BAQsFAAOCAQEAdHRiLqlYAyGNMPj6wzJt3XmwDbU5yEWor4q+GmA9
12+
fupirWXeSqKiqngDfvHlQNKgDlm10Kuk7LDVUcAP27Xnv/uFmHIUF+4g/eIjxvog
13+
RorUD2I9hi0Wyww7E8th/JfnuDX4YbIQrv1r5P4JaCoc0C2NBd1hO1Er2GdNEoXm
14+
UYoZg6/P5YQkWSLYtLPswb/Hf63DvzG94H6HnFBYlumt/5xYLrfD1Lx8099wZVdR
15+
qWXSi/tYi0HJGGUynZCvjdUu5En7eDoyWclGHz3stOUkBlz0efz01bxpiGsE/rRG
16+
Xr6qJt45N0Zktytk5TphoeDAeFB5ZHRRatZsg9CyZGoaIA==
17+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpQIBAAKCAQEAwYP2gguYxFymUaRw11JkywFHsLbKg+0v2JGndXYEqmJFm9C5
3+
cdJCx1xLROmTq+chZBMuQKNVFrAv0Gg7PArcVlC2L2JdEyce7OXSBAZkaIclnY/9
4+
59ZpW+dyYfcK7ag0JV4+GBgAw6KKlgZA9yOdDaRS8wuV1n35YL4RxoP4UD+Rpxw9
5+
f6Q7FCCpDQyc9AV+ejQXyDKU5pz6i4o6xfJr6OVy2Ard3ELvf8j4J2rzGs4nyVc5
6+
jGCCV4FlFc1ZZSGvOiafitQ/3A0GtDMUIZyohL5COUAKOXKfSA9T/9kZVFnyBleg
7+
jykNi4GVHJam33vT9k8W6LJ+JqwAABzyAncLtwIDAQABAoIBAQC+gC43rzrgc2S3
8+
km4TSmU3AzeT2x5Z6TDkvd5gX6IQKVXlIgCs8BQVNeJTIK3i2FGits8diqzE/QTU
9+
4QcPAJIP1rzCwM5ngGeNRmEM3U4TKJf7GDkX9ZcahimwDwaPFrre3nu6NEbsUCKl
10+
tdpWcJS3TUDrSkhjMvhAKFxPVLMqKvNK3xg81OmubYDHJ7dmmobJDzklmRlrFCNL
11+
RcQSUYnYruIY4pLmpxVvkFShdxy4oM3f6qanp/nxVvO1of+bqL9fQlgLXSYt83eK
12+
qlUKDdZx/IfckS/DU/8s/PnC5KbrAZB/vNcTIN7USsuAZgP/a8XDLrcH121YZEjW
13+
gIwleYUBAoGBAOtkBKYu+DqlB6xsDJ4XiNji1J19Kmkea+rK5YH21RJlAW3Uh9Y+
14+
tu9J0iQqqQgyIT+v8U0b6uvKjGUoKYbGha7Cl2X93tFL6QGhfewWF/Yqnzr6cBip
15+
IGfHTTWRkZCeNyDII2VEn4B5E+0emCp0p9B8ffr/bUHgFr/wLLD/sDyXAoGBANJ1
16+
XYLK+ilWvL3iV9pcvbuHMP7igXP/wOJsoOpMNixBVhzSm4FZWk4duHdRvMQysk8f
17+
KFiEx+0EJwYyBpbnBCRemhFzergV+6a8tJ4x4rBaVOQBTdLJLPypU5tcLP0iWX+b
18+
oyp7mRT+1ffQ2RcFZBRN6bOvcFrkwdiEl6lglu3hAoGBALnTLpxmrg3V5FXowpk3
19+
aRAXGdPuUMHFg1pKrJ5J1vF7jYI/6rBmuBH1jBCDIQfYU0ksw2ilJnLYZrcg2o+M
20+
P1K0ScL5hKJjs+FWtMrgsi/ie+uac030jiF/Q+OLNIgfbtPRS6gRYX2Rl/p0UZoK
21+
l8RN00KHzJ/ZoPwLRazBXUanAoGBAKzVv9bS1MCwP859HILymMpxyvX3lDJsPb51
22+
UW0462BKw+plt1lxxOzUEZLD6I8Dx1WdE+gmG331ZAr9eFXjII6xtjtQp96YBxO2
23+
c2pbM3x6oq6gt4W8uxpAAK5c84Fq/S8D5OrVmDEa2yNqO25hegAGwD9Ve6LZrKwg
24+
r+Bkt25hAoGAdfY0dHAbZpBSSBixb+XsnDxAne8I4OwTOpExdSH1V1Q85CdTlYLq
25+
FoLuy4rqdrF9kFnasn66diaqUtVaubdG8GyJXTGh1rpOGTjhAjAWCl27fOcO/Ffv
26+
7MqsNI8qhOwIJYaBZ8PXtROp5rf3reqjHu9KqfMj+a2sADiF4bW7KYg=
27+
-----END RSA PRIVATE KEY-----

0 commit comments

Comments
 (0)