diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 83459803e8..04a63d99d8 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -38,27 +38,27 @@ jobs: npm run build -- --target i686-pc-windows-msvc npm run test target: i686-pc-windows-msvc - - host: ubuntu-22.04 + - host: ubuntu-latest target: x86_64-unknown-linux-gnu use-cross: true build: | set -e CARGO=cross npm run build -- --target x86_64-unknown-linux-gnu - - host: ubuntu-22.04 + - host: ubuntu-latest target: i686-unknown-linux-gnu use-cross: true build: | set -e source scripts/configure-cross.sh i686-unknown-linux-gnu CARGO=cross npm run build -- --target i686-unknown-linux-gnu - - host: ubuntu-22.04 + - host: ubuntu-latest target: armv7-unknown-linux-gnueabihf use-cross: true build: | set -e source scripts/configure-cross.sh armv7-unknown-linux-gnueabihf CARGO=cross npm run build -- --target armv7-unknown-linux-gnueabihf - - host: ubuntu-22.04 + - host: ubuntu-latest target: x86_64-unknown-linux-musl use-cross: true build: | @@ -67,14 +67,14 @@ jobs: - host: macos-latest target: aarch64-apple-darwin build: npm run build -- --target aarch64-apple-darwin - - host: ubuntu-22.04 + - host: ubuntu-latest target: aarch64-unknown-linux-gnu use-cross: true build: | set -e source scripts/configure-cross.sh aarch64-unknown-linux-gnu CARGO=cross npm run build -- --target aarch64-unknown-linux-gnu - - host: ubuntu-22.04 + - host: ubuntu-latest target: aarch64-unknown-linux-musl use-cross: true build: | @@ -208,22 +208,23 @@ jobs: - host: macos-latest target: x86_64-apple-darwin architecture: x64 - - host: ubuntu-22.04 + - host: ubuntu-latest target: x86_64-unknown-linux-gnu - - host: ubuntu-22.04 + - host: ubuntu-latest target: x86_64-unknown-linux-musl - - host: ubuntu-22.04 + - host: ubuntu-latest target: aarch64-unknown-linux-gnu platform: linux/arm64 - - host: ubuntu-22.04 + - host: ubuntu-latest target: aarch64-unknown-linux-musl platform: linux/arm64 - - host: ubuntu-22.04 + - host: ubuntu-latest target: armv7-unknown-linux-gnueabihf platform: linux/arm/v7 node: - "18" - "20" + - "22" runs-on: ${{ matrix.settings.host }} steps: - uses: actions/checkout@v4 @@ -250,10 +251,10 @@ jobs: if: ${{ matrix.settings.platform }} - name: Test bindings run: npm run test - if: ${{ matrix.settings.host != 'ubuntu-22.04' }} + if: ${{ matrix.settings.host != 'ubuntu-latest' }} - name: Setup and run tests uses: addnab/docker-run-action@v3 - if: ${{ matrix.settings.host == 'ubuntu-22.04' && !endsWith(matrix.settings.target, 'musl') }} + if: ${{ matrix.settings.host == 'ubuntu-latest' && !endsWith(matrix.settings.target, 'musl') }} with: image: ${{ format('node:{0}-slim', matrix.node) }} options: "-v ${{ github.workspace }}:/build -w /build --cap-add=IPC_LOCK ${{ matrix.settings.platform && format('--platform={0}', matrix.settings.platform) }}" @@ -263,7 +264,7 @@ jobs: cd packages/secrets && dbus-run-session -- bash scripts/linux-test.sh - name: Setup and run tests (MUSL) uses: addnab/docker-run-action@v3 - if: ${{ matrix.settings.host == 'ubuntu-22.04' && endsWith(matrix.settings.target, 'musl') }} + if: ${{ matrix.settings.host == 'ubuntu-latest' && endsWith(matrix.settings.target, 'musl') }} with: image: ${{ format('node:{0}-alpine', matrix.node) }} options: "-v ${{ github.workspace }}:/build -w /build --cap-add=IPC_LOCK ${{ matrix.settings.platform && format('--platform={0}', matrix.settings.platform) }}" diff --git a/packages/secrets/CHANGELOG.md b/packages/secrets/CHANGELOG.md index 8e88d6b1ca..e7ea7e74d9 100644 --- a/packages/secrets/CHANGELOG.md +++ b/packages/secrets/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the Zowe Secrets SDK package will be documented in this file. +## Recent Changes + +- BugFix: Reduced number of keychain unlock prompts on MacOS for simultaneous access to secrets by multiple instances of the same application. [#2394](https://github.com/zowe/zowe-cli/pull/2394) + ## `8.1.2` - BugFix: Updated dependencies for technical currency. [#2289](https://github.com/zowe/zowe-cli/pull/2289) @@ -52,4 +56,4 @@ All notable changes to the Zowe Secrets SDK package will be documented in this f ## `7.18.0` - Initial release. -- `keyring` module added for interacting with OS-specific keyring/credential vaults. See [src/keyring](src/keyring/README.md) for information on this native module and how it can be used. \ No newline at end of file +- `keyring` module added for interacting with OS-specific keyring/credential vaults. See [src/keyring](src/keyring/README.md) for information on this native module and how it can be used. diff --git a/packages/secrets/core/Cargo.lock b/packages/secrets/core/Cargo.lock index 42f00a8154..6057e22729 100644 --- a/packages/secrets/core/Cargo.lock +++ b/packages/secrets/core/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "autocfg" @@ -52,6 +52,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fmutex" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e84c17070603126a7b0cd07d0ecc8e8cca4d15b67934ac2740286a84f3086c" +dependencies = [ + "libc", +] + [[package]] name = "futures-channel" version = "0.3.29" @@ -348,6 +357,7 @@ dependencies = [ "cfg-if", "core-foundation", "core-foundation-sys", + "fmutex", "gio", "glib", "glib-sys", diff --git a/packages/secrets/core/Cargo.toml b/packages/secrets/core/Cargo.toml index b178909c50..30c0a4125f 100644 --- a/packages/secrets/core/Cargo.toml +++ b/packages/secrets/core/Cargo.toml @@ -22,10 +22,11 @@ version = "0.48.0" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9.3" core-foundation-sys = "0.8.4" +fmutex = "0.1.0" [target.'cfg(any(target_os = "freebsd", target_os = "linux"))'.dependencies] +gio = "0.18.2" glib = "0.18.2" glib-sys = "0.18.1" -gio = "0.18.2" libsecret = "0.4.0" -libsecret-sys = "0.4.0" \ No newline at end of file +libsecret-sys = "0.4.0" diff --git a/packages/secrets/core/src/os/error.rs b/packages/secrets/core/src/os/error.rs index 7d7c251d32..572ca2489d 100644 --- a/packages/secrets/core/src/os/error.rs +++ b/packages/secrets/core/src/os/error.rs @@ -11,7 +11,6 @@ pub enum KeyringError { #[error("[keyring] {name:?} library returned an error:\n\n{details:?}")] Library { name: String, details: String }, - #[cfg(not(target_os = "macos"))] #[error("[keyring] An OS error has occurred:\n\n{0}")] Os(String), diff --git a/packages/secrets/core/src/os/mac/mod.rs b/packages/secrets/core/src/os/mac/mod.rs index 8e4554d06e..57587f1d4c 100644 --- a/packages/secrets/core/src/os/mac/mod.rs +++ b/packages/secrets/core/src/os/mac/mod.rs @@ -11,6 +11,7 @@ use error::Error; use crate::os::mac::error::ERR_SEC_ITEM_NOT_FOUND; use crate::os::mac::keychain_search::{KeychainSearch, SearchResult}; +use fmutex::Guard; use keychain::SecKeychain; impl From for KeyringError { @@ -22,6 +23,27 @@ impl From for KeyringError { } } +impl From for KeyringError { + fn from(error: std::io::Error) -> Self { + KeyringError::Os(error.to_string()) + } +} + +fn keyring_mutex() -> Result { + // MacOS shows keychain prompt after secret has been modified by another process. We use cross-process mutex to + // block keychain access if there are multiple concurrent keychain operations invoked by the same process. This + // prevents multiple instances of the same app (e.g. VS Code) from triggering several keychain prompts at once. + let exe_path = std::env::current_exe() + .unwrap() + .to_string_lossy() + .replace(std::path::MAIN_SEPARATOR, "_"); + let lock_path = std::env::temp_dir() + .join(format!("zowe_{}_{}.lock", env!("CARGO_PKG_NAME"), exe_path)); + std::fs::OpenOptions::new().create(true).write(true).open(&lock_path) + .and_then(|_| fmutex::lock(lock_path)) + .map_err(KeyringError::from) +} + /// /// Attempts to set a password for a given service and account. /// @@ -38,6 +60,7 @@ pub fn set_password( password: &String, ) -> Result { let keychain = SecKeychain::default().unwrap(); + let _lock = keyring_mutex().unwrap(); match keychain.set_password(service.as_str(), account.as_str(), password.as_bytes()) { Ok(()) => Ok(true), Err(err) => Err(KeyringError::from(err)), @@ -56,6 +79,7 @@ pub fn set_password( /// pub fn get_password(service: &String, account: &String) -> Result, KeyringError> { let keychain = SecKeychain::default().unwrap(); + let _lock = keyring_mutex().unwrap(); match keychain.find_password(service.as_str(), account.as_str()) { Ok((pw, _)) => Ok(Some(String::from_utf8(pw.to_owned())?)), Err(err) if err.code() == ERR_SEC_ITEM_NOT_FOUND => Ok(None), @@ -83,6 +107,7 @@ pub fn find_password(service: &String) -> Result, KeyringError> { } let keychain = SecKeychain::default().unwrap(); + let _lock = keyring_mutex().unwrap(); match keychain.find_password(cred_attrs[0], cred_attrs[1]) { Ok((pw, _)) => { let pw_str = String::from_utf8(pw.to_owned())?; @@ -104,6 +129,7 @@ pub fn find_password(service: &String) -> Result, KeyringError> { /// pub fn delete_password(service: &String, account: &String) -> Result { let keychain = SecKeychain::default().unwrap(); + let _lock = keyring_mutex().unwrap(); match keychain.find_password(service.as_str(), account.as_str()) { Ok((_, item)) => { item.delete()?; @@ -128,6 +154,7 @@ pub fn find_credentials( service: &String, credentials: &mut Vec<(String, String)>, ) -> Result { + let _lock = keyring_mutex().unwrap(); match KeychainSearch::new() .label(service.as_str()) .with_attrs() diff --git a/packages/secrets/package.json b/packages/secrets/package.json index 88df5e6fa8..62d4fdebba 100644 --- a/packages/secrets/package.json +++ b/packages/secrets/package.json @@ -28,7 +28,8 @@ "ava": "^6.0.0" }, "ava": { - "timeout": "3m" + "timeout": "3m", + "workerThreads": false }, "engines": { "node": ">=14" diff --git a/packages/secrets/src/keyring/Cargo.lock b/packages/secrets/src/keyring/Cargo.lock index e1b77b0474..cb6ec93e33 100644 --- a/packages/secrets/src/keyring/Cargo.lock +++ b/packages/secrets/src/keyring/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -80,6 +80,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fmutex" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e84c17070603126a7b0cd07d0ecc8e8cca4d15b67934ac2740286a84f3086c" +dependencies = [ + "libc", +] + [[package]] name = "futures-channel" version = "0.3.28" @@ -482,6 +491,7 @@ dependencies = [ "cfg-if", "core-foundation", "core-foundation-sys", + "fmutex", "gio", "glib", "glib-sys", diff --git a/packages/secrets/src/keyring/Cross.toml b/packages/secrets/src/keyring/Cross.toml index 106f07ec29..5a5f5826f8 100644 --- a/packages/secrets/src/keyring/Cross.toml +++ b/packages/secrets/src/keyring/Cross.toml @@ -15,11 +15,11 @@ passthrough = [ image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main" [target.aarch64-unknown-linux-musl] -image = "rust:alpine" +image = "rust:alpine3.20" pre-build = [ "wget -qO- https://musl.cc/aarch64-linux-musl-cross.tgz | tar -xzC / && export PATH=\"/aarch64-linux-musl-cross/bin:$PATH\"", "apk add --no-cache musl-dev pkgconfig", - "apk add -p /aarch64-linux-musl-cross --initdb --arch aarch64 --allow-untrusted -X \"https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/\" --no-cache --no-scripts libsecret-dev", + "apk add -p /aarch64-linux-musl-cross --initdb --arch aarch64 --allow-untrusted -X $(head -n 1 /etc/apk/repositories) --no-cache --no-scripts libsecret-dev", "rustup target add aarch64-unknown-linux-musl" ] @@ -33,5 +33,5 @@ image = "ghcr.io/cross-rs/i686-unknown-linux-gnu:main" image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main" [target.x86_64-unknown-linux-musl] -image = "rust:alpine" +image = "rust:alpine3.20" pre-build = ["apk add libsecret-dev musl-dev"]