From afa6e7ab5bb26972acbfe19af41dd3e6ac5df363 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Thu, 5 Feb 2026 13:35:31 -0500 Subject: [PATCH] feat: mssql integrated auth (gssapi) (#7760) * feat: mssql integrated auth (kerberos/ntlm) * install krb5 headers * also make it work for windows --------- Co-authored-by: Ruben Fiszel --- .github/workflows/backend-check.yml | 8 +- .github/workflows/claude.yml | 4 +- .github/workflows/git-commands.yaml | 4 +- Dockerfile | 2 +- backend/Cargo.lock | 130 +++++++++++++++++- backend/Cargo.toml | 8 +- backend/windmill-worker/Cargo.toml | 2 + backend/windmill-worker/src/mssql_executor.rs | 21 ++- docker/RHEL8/Dockerfile | 2 +- docker/RHEL9/Dockerfile | 2 +- 10 files changed, 163 insertions(+), 20 deletions(-) diff --git a/.github/workflows/backend-check.yml b/.github/workflows/backend-check.yml index 11f8af5c29..e70c0c56f6 100644 --- a/.github/workflows/backend-check.yml +++ b/.github/workflows/backend-check.yml @@ -33,10 +33,10 @@ jobs: with: fetch-depth: 0 - - name: install xmlsec1 + - name: install xmlsec1 and gssapi run: | sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev + sudo apt-get install -y libxml2-dev libxmlsec1-dev libkrb5-dev - uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -100,10 +100,10 @@ jobs: token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }} fetch-depth: 0 - - name: install xmlsec1 + - name: install xmlsec1 and gssapi run: | sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev + sudo apt-get install -y libxml2-dev libxmlsec1-dev libkrb5-dev - name: Substitute EE code (EE logic is behind feature flag) run: | diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index a95e26d66b..fcad6ef314 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -75,10 +75,10 @@ jobs: npm install npm run generate-backend-client - - name: install xmlsec1 + - name: install xmlsec1 and gssapi run: | sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev + sudo apt-get install -y libxml2-dev libxmlsec1-dev libkrb5-dev - uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/git-commands.yaml b/.github/workflows/git-commands.yaml index 7e8a5e2acf..c098f856e2 100644 --- a/.github/workflows/git-commands.yaml +++ b/.github/workflows/git-commands.yaml @@ -68,11 +68,11 @@ jobs: with: workspaces: "./backend -> target" - - name: Install xmlsec build-time deps + - name: Install xmlsec and gssapi build-time deps run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ - pkg-config libxml2-dev libssl-dev \ + pkg-config libxml2-dev libssl-dev libkrb5-dev \ xmlsec1 libxmlsec1-dev libxmlsec1-openssl - name: Run update-sqlx script diff --git a/Dockerfile b/Dockerfile index 572e84e265..341cceeb8d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -97,7 +97,7 @@ ARG features="" COPY --from=planner /windmill/recipe.json recipe.json -RUN apt-get update && apt-get install -y libxml2-dev=2.9.* libxmlsec1-dev=1.2.* clang=1:14.0-55.* libclang-dev=1:14.0-55.* cmake=3.25.* && \ +RUN apt-get update && apt-get install -y libxml2-dev=2.9.* libxmlsec1-dev=1.2.* libkrb5-dev clang=1:14.0-55.* libclang-dev=1:14.0-55.* cmake=3.25.* && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9ed142ec48..7caaf2f6ca 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1632,6 +1632,26 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.10.5", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn 2.0.114", +] + [[package]] name = "bindgen" version = "0.72.1" @@ -6104,6 +6124,17 @@ dependencies = [ "unicode-width 0.2.2", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -6113,7 +6144,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -7916,6 +7947,28 @@ dependencies = [ "cc", ] +[[package]] +name = "libgssapi" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e668df13f2e97f3eed52d9301f6b1c4c1ccfccc30eab9e6628e4a8c1fc3546" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "lazy_static", + "libgssapi-sys", +] + +[[package]] +name = "libgssapi-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7518e6902e94f92e7c7271232684b60988b4bd813529b4ef9d97aead96956ae8" +dependencies = [ + "bindgen 0.71.1", + "pkg-config", +] + [[package]] name = "libloading" version = "0.7.4" @@ -8331,6 +8384,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "md5" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d" + [[package]] name = "measure_time" version = "0.9.0" @@ -8482,7 +8541,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -8493,7 +8552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -10632,6 +10691,19 @@ dependencies = [ "nibble_vec", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -10654,6 +10726,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -10674,6 +10756,15 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -10712,6 +10803,15 @@ dependencies = [ "rand 0.9.0", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "range-alloc" version = "0.1.4" @@ -13786,8 +13886,7 @@ dependencies = [ [[package]] name = "tiberius" version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1446cb4198848d1562301a3340424b4f425ef79f35ef9ee034769a9dd92c10d" +source = "git+https://github.com/prisma/tiberius?rev=59db57960a14b422fb3a1309aa4aa47880896ff8#59db57960a14b422fb3a1309aa4aa47880896ff8" dependencies = [ "async-trait", "asynchronous-codec", @@ -13798,6 +13897,7 @@ dependencies = [ "encoding_rs", "enumflags2", "futures-util", + "libgssapi", "num-traits", "once_cell", "pin-project-lite", @@ -13810,6 +13910,7 @@ dependencies = [ "tokio-util", "tracing", "uuid", + "winauth", ] [[package]] @@ -15131,6 +15232,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -15496,6 +15603,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winauth" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f820cd208ce9c6b050812dc2d724ba98c6c1e9db5ce9b3f58d925ae5723a5e6" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "md5", + "rand 0.7.3", + "winapi", +] + [[package]] name = "windmill" version = "1.626.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b8dd4eb59e..d61d4575ee 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -98,15 +98,17 @@ mysql = ["windmill-worker/mysql"] oracledb = ["windmill-worker/oracledb"] duckdb = ["windmill-worker/duckdb"] mssql = ["windmill-worker/mssql"] +mssql-kerberos = ["windmill-worker/mssql-kerberos"] # Linux/Unix integrated auth +mssql-winauth = ["windmill-worker/mssql-winauth"] # Windows integrated auth bigquery = ["windmill-worker/bigquery"] php = ["windmill-worker/php"] csharp = ["windmill-worker/csharp"] nu = ["windmill-worker/nu"] java = ["windmill-worker/java"] ruby = ["windmill-worker/ruby"] -all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java", "ruby"] +all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql-kerberos", "bigquery", "csharp", "nu", "php", "java", "ruby"] # For windows we have another set of languages enabled -all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java"] +all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql-winauth", "bigquery", "csharp", "nu", "php", "java"] all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "embedding", "parquet", "prometheus", "flow_testing", "openidconnect", "cloud", "jemalloc", "tantivy", "sqlx", "kafka", "nats", "otel", "dind", "websocket", "http_trigger", "postgres_trigger", "mcp", "mqtt_trigger", "sqs_trigger", "gcp_trigger", "smtp", "stripe", @@ -114,6 +116,8 @@ all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "embeddin [patch.crates-io] object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" } +# Use tiberius main branch for libgssapi 0.8.1 fix (https://github.com/prisma/tiberius/issues/343) +tiberius = { git = "https://github.com/prisma/tiberius", rev = "59db57960a14b422fb3a1309aa4aa47880896ff8" } [dependencies] anyhow.workspace = true diff --git a/backend/windmill-worker/Cargo.toml b/backend/windmill-worker/Cargo.toml index 8ba01c71ab..76d1fbbdcf 100644 --- a/backend/windmill-worker/Cargo.toml +++ b/backend/windmill-worker/Cargo.toml @@ -15,6 +15,8 @@ mcp = ["dep:windmill-mcp"] prometheus = ["dep:prometheus", "windmill-common/prometheus"] enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "dep:pem", "dep:tokio-util"] mssql = ["dep:tiberius"] +mssql-kerberos = ["mssql", "tiberius/integrated-auth-gssapi"] # Linux/Unix integrated auth +mssql-winauth = ["mssql", "tiberius/winauth"] # Windows integrated auth bigquery = ["dep:gcp_auth"] benchmark = ["windmill-queue/benchmark", "windmill-common/benchmark"] parquet = ["windmill-common/parquet", "dep:object_store"] diff --git a/backend/windmill-worker/src/mssql_executor.rs b/backend/windmill-worker/src/mssql_executor.rs index d9f1dda928..e836018235 100644 --- a/backend/windmill-worker/src/mssql_executor.rs +++ b/backend/windmill-worker/src/mssql_executor.rs @@ -46,6 +46,7 @@ struct MssqlDatabase { #[serde(default, deserialize_with = "empty_as_none")] ca_cert: Option, encrypt: Option, + integrated_auth: Option, } #[derive(Debug, Deserialize)] @@ -129,7 +130,23 @@ pub async fn do_mssql( } // Handle authentication based on available credentials - if let Some(token_value) = &database.aad_token { + if database.integrated_auth.unwrap_or(false) { + #[cfg(any(feature = "mssql-kerberos", feature = "mssql-winauth"))] + { + config.authentication(AuthMethod::Integrated); + #[cfg(feature = "mssql-kerberos")] + let logs = format!("\nUsing Integrated Authentication (Kerberos/GSSAPI)"); + #[cfg(feature = "mssql-winauth")] + let logs = format!("\nUsing Integrated Authentication (Windows SSPI)"); + append_logs(&job.id, &job.workspace_id, logs, conn).await; + } + #[cfg(not(any(feature = "mssql-kerberos", feature = "mssql-winauth")))] + { + return Err(Error::BadRequest( + "Integrated authentication is not available in this build. Requires mssql-kerberos (Linux) or mssql-winauth (Windows) feature.".to_string(), + )); + } + } else if let Some(token_value) = &database.aad_token { if let Some(token) = &token_value.token { config.authentication(AuthMethod::aad_token(token)); } else { @@ -141,7 +158,7 @@ pub async fn do_mssql( config.authentication(AuthMethod::sql_server(user.clone(), password.clone())); } else { return Err(Error::BadRequest( - "Neither AAD token nor username/password credentials are set".to_string(), + "No authentication method configured. Set integrated_auth, aad_token, or user/password.".to_string(), )); } diff --git a/docker/RHEL8/Dockerfile b/docker/RHEL8/Dockerfile index eaaa01ea87..cbadd828b3 100644 --- a/docker/RHEL8/Dockerfile +++ b/docker/RHEL8/Dockerfile @@ -62,7 +62,7 @@ RUN --mount=type=secret,id=rh_username \ RUN subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms RUN yum update -y && \ - yum install -y perl-interpreter perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel clang llvm-devel cmake libtool-ltdl-devel + yum install -y perl-interpreter perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel clang llvm-devel cmake libtool-ltdl-devel # RUN --mount=type=cache,target=/usr/local/cargo/registry \ # CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json diff --git a/docker/RHEL9/Dockerfile b/docker/RHEL9/Dockerfile index ebcba5ecc4..aef3b61fa0 100644 --- a/docker/RHEL9/Dockerfile +++ b/docker/RHEL9/Dockerfile @@ -62,7 +62,7 @@ RUN --mount=type=secret,id=rh_username \ RUN subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms RUN yum update -y && \ - yum install -y perl-FindBin perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel clang llvm-devel cmake libtool-ltdl-devel + yum install -y perl-FindBin perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel krb5-devel clang llvm-devel cmake libtool-ltdl-devel # RUN --mount=type=cache,target=/usr/local/cargo/registry \ # CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json