Remote config fixes

This commit is contained in:
crschnick
2026-01-01 22:32:51 +00:00
parent a1d3218116
commit eef19346dd
14 changed files with 91 additions and 63 deletions
@@ -52,7 +52,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
private ObservableValue<FilePath> prompt;
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
Property<DataStoreEntryRef<T>> fileSystem,
ObservableValue<DataStoreEntryRef<T>> fileSystem,
Property<FilePath> filePath,
ContextualFileReferenceSync sync,
List<PreviousFileReference> previousFileReferences,
@@ -66,9 +66,6 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
fileSystem.subscribe(val -> {
this.fileSystem.setValue(val);
});
this.fileSystem.addListener((observable, oldValue, newValue) -> {
fileSystem.setValue(newValue != null ? newValue.asNeeded() : null);
});
this.filePath = filePath;
}
@@ -85,7 +82,6 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
fileStore -> {
if (fileStore != null) {
filePath.setValue(fileStore.getPath());
fileSystem.setValue(fileStore.getFileSystem());
}
},
false,
@@ -4,14 +4,18 @@ import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.InputGroupComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.FileSystemStore;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.platform.OptionsChoiceBuilder;
import io.xpipe.app.secret.EncryptedValue;
import io.xpipe.app.secret.SecretRetrievalStrategy;
import io.xpipe.app.secret.SecretStrategyChoiceConfig;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStorageUserHandler;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.*;
import io.xpipe.ext.base.identity.ssh.SshIdentityStrategy;
import io.xpipe.ext.base.identity.ssh.SshIdentityStrategyChoiceConfig;
@@ -40,6 +44,7 @@ public class IdentityChoiceBuilder {
boolean allowAgentForward;
String userChoiceTranslationKey;
ObservableValue<String> passwordChoiceTranslationKey;
ObservableValue<DataStoreEntryRef<ShellStore>> fileSystem;
public IdentityChoiceBuilder(
ObjectProperty<IdentityValue> identity,
@@ -60,6 +65,7 @@ public class IdentityChoiceBuilder {
this.allowAgentForward = allowAgentForward;
this.userChoiceTranslationKey = userChoiceTranslationKey;
this.passwordChoiceTranslationKey = new ReadOnlyStringWrapper(passwordChoiceTranslationKey);
this.fileSystem = new ReadOnlyObjectWrapper<>(DataStorage.get().local().ref());
}
public static OptionsBuilder ssh(ObjectProperty<IdentityValue> identity, boolean requireUser) {
@@ -147,6 +153,7 @@ public class IdentityChoiceBuilder {
.allowAgentForward(allowAgentForward)
.allowKeyFileSync(true)
.perUserKeyFileCheck(() -> false)
.fileSystem(fileSystem)
.build();
if (keyInput) {
@@ -97,7 +97,7 @@ public class CustomAgentStrategy implements SshIdentityStrategy {
@Override
public void buildCommand(CommandBuilder builder) {
builder.environment("SSH_AUTH_SOCK", sc -> {
if (sc.getOsType() == OsType.WINDOWS) {
if (!sc.isLocal() || sc.getOsType() == OsType.WINDOWS) {
return null;
}
@@ -113,8 +113,8 @@ public class CustomAgentStrategy implements SshIdentityStrategy {
}
@Override
public List<KeyValue> configOptions() {
var file = SshIdentityStrategy.getPublicKeyPath(publicKey);
public List<KeyValue> configOptions(ShellControl sc) throws Exception {
var file = SshIdentityStrategy.getPublicKeyPath(sc, publicKey);
return List.of(
new KeyValue("IdentitiesOnly", file.isPresent() ? "yes" : "no"),
new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"),
@@ -48,12 +48,22 @@ public class CustomPkcs11LibraryStrategy implements SshIdentityStrategy {
.nameAndDescription("pkcs11Library")
.addComp(
new ContextualFileReferenceChoiceComp(
new ReadOnlyObjectWrapper<>(
DataStorage.get().local().ref()),
config.getFileSystem() != null ? config.getFileSystem() : new ReadOnlyObjectWrapper<>(DataStorage.get().local().ref()),
file,
null,
List.of(),
e -> e.equals(DataStorage.get().local()),
e -> {
if (config.getFileSystem() == null) {
return e.equals(DataStorage.get().local());
}
var fs = config.getFileSystem().getValue();
if (fs == null) {
return e.equals(DataStorage.get().local());
} else {
return e.equals(fs.get());
}
},
false),
file)
.nonNull()
@@ -95,7 +105,7 @@ public class CustomPkcs11LibraryStrategy implements SshIdentityStrategy {
}
@Override
public List<KeyValue> configOptions() {
public List<KeyValue> configOptions(ShellControl sc) {
return List.of(
new KeyValue("IdentitiesOnly", "no"),
new KeyValue("PKCS11Provider", "\"" + file.toString() + "\""),
@@ -103,8 +103,8 @@ public class GpgAgentStrategy implements SshIdentityStrategy {
}
@Override
public List<KeyValue> configOptions() {
var file = SshIdentityStrategy.getPublicKeyPath(publicKey);
public List<KeyValue> configOptions(ShellControl sc) throws Exception {
var file = SshIdentityStrategy.getPublicKeyPath(sc, publicKey);
return List.of(
new KeyValue("IdentitiesOnly", file.isPresent() ? "yes" : "no"),
new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"),
@@ -136,7 +136,7 @@ public class InPlaceKeyStrategy implements SshIdentityStrategy {
return;
}
var file = getTargetFilePath();
var file = getTargetFilePath(parent);
if (parent.view().fileExists(file)) {
return;
}
@@ -156,18 +156,20 @@ public class InPlaceKeyStrategy implements SshIdentityStrategy {
.execute();
}
LocalFileTracker.deleteOnExit(file.asLocalPath());
if (parent.isLocal()) {
LocalFileTracker.deleteOnExit(file.asLocalPath());
}
}
@Override
public void buildCommand(CommandBuilder builder) {}
@Override
public List<KeyValue> configOptions() {
public List<KeyValue> configOptions(ShellControl sc) {
return List.of(
new KeyValue("IdentitiesOnly", "yes"),
new KeyValue("IdentityAgent", "none"),
new KeyValue("IdentityFile", "\"" + getTargetFilePath() + "\""),
new KeyValue("IdentityFile", "\"" + getTargetFilePath(sc) + "\""),
new KeyValue("PKCS11Provider", "none"));
}
@@ -176,11 +178,10 @@ public class InPlaceKeyStrategy implements SshIdentityStrategy {
return password;
}
private FilePath getTargetFilePath() {
var temp = AppSystemInfo.ofCurrent()
.getTemp()
.resolve("xpipe-"
private FilePath getTargetFilePath(ShellControl sc) {
var temp = sc.getSystemTemporaryDirectory()
.join("xpipe-"
+ Math.abs(Objects.hash(this, AppSystemInfo.ofCurrent().getUser())) + ".key");
return FilePath.of(temp);
return temp;
}
}
@@ -11,6 +11,7 @@ import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.platform.OptionsChoiceBuilder;
import io.xpipe.app.process.CommandBuilder;
import io.xpipe.app.process.LocalShell;
import io.xpipe.app.process.ShellControl;
import io.xpipe.app.secret.SecretRetrievalStrategy;
import io.xpipe.app.secret.SecretStrategyChoiceConfig;
@@ -37,8 +38,6 @@ import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import org.kordamp.ikonli.javafx.FontIcon;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@Value
@@ -104,20 +103,21 @@ public class KeyFileStrategy implements SshIdentityStrategy {
});
var generateButton = new ButtonComp(null, new LabelGraphic.IconGraphic("mdi2c-cog-refresh-outline"), () -> {
ThreadHelper.runFailableAsync(() -> {
Path path = keyPath.get().asLocalPath();
if (!Files.exists(path)) {
var sc = config.getFileSystem() != null ? config.getFileSystem().getValue().getStore().getOrStartSession() : LocalShell.getShell();
var path = keyPath.get();
if (!sc.view().fileExists(path)) {
return;
}
var pubKeyPath = Path.of(path + ".pub");
if (Files.exists(pubKeyPath)) {
var contents = Files.readString(pubKeyPath).strip();
var pubKeyPath = FilePath.of(path + ".pub");
if (sc.view().fileExists(pubKeyPath)) {
var contents = sc.view().readTextFile(pubKeyPath).strip();
Platform.runLater(() -> {
publicKey.set(contents);
});
}
var contents = Files.readAllBytes(path);
var contents = sc.view().readRawFile(path);
var generated = ProcessControlProvider.get()
.generatePublicSshKey(InPlaceSecretValue.of(contents), keyPasswordProperty.get());
if (generated != null) {
@@ -143,12 +143,22 @@ public class KeyFileStrategy implements SshIdentityStrategy {
.description("locationDescription")
.addComp(
new ContextualFileReferenceChoiceComp(
new ReadOnlyObjectWrapper<>(
DataStorage.get().local().ref()),
config.getFileSystem() != null ? config.getFileSystem() : new ReadOnlyObjectWrapper<>(DataStorage.get().local().ref()),
keyPath,
config.isAllowKeyFileSync() ? sync : null,
List.of(),
e -> e.equals(DataStorage.get().local()),
e -> {
if (config.getFileSystem() == null) {
return e.equals(DataStorage.get().local());
}
var fs = config.getFileSystem().getValue();
if (fs == null) {
return e.equals(DataStorage.get().local());
} else {
return e.equals(fs.get());
}
},
false),
keyPath)
.nonNull()
@@ -232,11 +242,11 @@ public class KeyFileStrategy implements SshIdentityStrategy {
public void buildCommand(CommandBuilder builder) {}
@Override
public List<KeyValue> configOptions() {
public List<KeyValue> configOptions(ShellControl sc) {
return List.of(
new KeyValue("IdentitiesOnly", "yes"),
new KeyValue("IdentityAgent", "none"),
new KeyValue("IdentityFile", "\"" + resolveFilePath().toString() + "\""),
new KeyValue("IdentityFile", "\"" + resolveFilePath(sc).toString() + "\""),
new KeyValue("PKCS11Provider", "none"));
}
@@ -245,8 +255,8 @@ public class KeyFileStrategy implements SshIdentityStrategy {
return password;
}
private FilePath resolveFilePath() {
var s = file.toLocalAbsoluteFilePath();
private FilePath resolveFilePath(ShellControl sc) {
var s = file.toAbsoluteFilePath(sc);
// The ~ is supported on all platforms, so manually replace it here for Windows
if (s.startsWith("~")) {
s = s.resolveTildeHome(FilePath.of(AppSystemInfo.ofCurrent().getUserHome()));
@@ -20,7 +20,7 @@ public class NoIdentityStrategy implements SshIdentityStrategy {
public void buildCommand(CommandBuilder builder) {}
@Override
public List<KeyValue> configOptions() {
public List<KeyValue> configOptions(ShellControl sc) {
// Don't use any agent keys to prevent too many authentication failures
return List.of(
new KeyValue("IdentitiesOnly", "yes"),
@@ -84,8 +84,8 @@ public class OpenSshAgentStrategy implements SshIdentityStrategy {
}
@Override
public List<KeyValue> configOptions() {
var file = SshIdentityStrategy.getPublicKeyPath(publicKey);
public List<KeyValue> configOptions(ShellControl sc) throws Exception {
var file = SshIdentityStrategy.getPublicKeyPath(sc, publicKey);
return List.of(
new KeyValue("IdentitiesOnly", file.isPresent() ? "yes" : "no"),
new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"),
@@ -61,8 +61,8 @@ public class OtherExternalAgentStrategy implements SshIdentityStrategy {
public void buildCommand(CommandBuilder builder) {}
@Override
public List<KeyValue> configOptions() {
var file = SshIdentityStrategy.getPublicKeyPath(publicKey);
public List<KeyValue> configOptions(ShellControl sc) throws Exception {
var file = SshIdentityStrategy.getPublicKeyPath(sc, publicKey);
return List.of(
new KeyValue("IdentitiesOnly", file.isPresent() ? "yes" : "no"),
new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"),
@@ -95,7 +95,7 @@ public class PageantStrategy implements SshIdentityStrategy {
@Override
public void buildCommand(CommandBuilder builder) {
builder.environment("SSH_AUTH_SOCK", parent -> {
if (parent.getOsType() == OsType.WINDOWS) {
if (parent.isLocal() && parent.getOsType() == OsType.WINDOWS) {
return getPageantWindowsPipe();
}
@@ -104,8 +104,8 @@ public class PageantStrategy implements SshIdentityStrategy {
}
@Override
public List<KeyValue> configOptions() {
var file = SshIdentityStrategy.getPublicKeyPath(publicKey);
public List<KeyValue> configOptions(ShellControl sc) throws Exception {
var file = SshIdentityStrategy.getPublicKeyPath(sc, publicKey);
return List.of(
new KeyValue("IdentitiesOnly", file.isPresent() ? "yes" : "no"),
new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"),
@@ -58,23 +58,22 @@ public interface SshIdentityStrategy {
return l;
}
static Optional<FilePath> getPublicKeyPath(String publicKey) {
static Optional<FilePath> getPublicKeyPath(ShellControl sc, String publicKey) throws Exception {
if (publicKey == null || publicKey.isBlank()) {
return Optional.empty();
}
var isFile = OsFileSystem.ofLocal().isProbableFilePath(publicKey);
if (isFile && Files.exists(Paths.get(publicKey))) {
return Optional.ofNullable(FilePath.parse(publicKey));
var isFile = OsFileSystem.of(sc.getOsType()).isProbableFilePath(publicKey);
if (isFile && sc.view().fileExists(FilePath.of(publicKey))) {
return Optional.of(FilePath.of(publicKey));
}
try {
var base = LocalShell.getShell().getSystemTemporaryDirectory().join("key.pub");
var file = LocalShell.getShell().view().writeTextFileDeterministic(base, publicKey.strip() + "\n");
var base = sc.getSystemTemporaryDirectory().join("key.pub");
var file = sc.view().writeTextFileDeterministic(base, publicKey.strip() + "\n");
if (OsType.ofLocal() != OsType.WINDOWS) {
LocalShell.getShell()
.command(CommandBuilder.of().add("chmod", "400").addFile(file))
if (sc.getOsType() != OsType.WINDOWS) {
sc.command(CommandBuilder.of().add("chmod", "400").addFile(file))
.executeAndCheck();
}
@@ -95,7 +94,7 @@ public interface SshIdentityStrategy {
void buildCommand(CommandBuilder builder);
List<KeyValue> configOptions();
List<KeyValue> configOptions(ShellControl sc) throws Exception;
default SecretRetrievalStrategy getAskpassStrategy() {
return new SecretNoneStrategy();
@@ -1,5 +1,9 @@
package io.xpipe.ext.base.identity.ssh;
import io.xpipe.app.ext.FileSystemStore;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.storage.DataStoreEntryRef;
import javafx.beans.value.ObservableValue;
import lombok.Builder;
import lombok.Value;
@@ -12,4 +16,5 @@ public class SshIdentityStrategyChoiceConfig {
Supplier<Boolean> perUserKeyFileCheck;
boolean allowKeyFileSync;
boolean allowAgentForward;
ObservableValue<DataStoreEntryRef<ShellStore>> fileSystem;
}
@@ -26,10 +26,9 @@ import java.util.List;
@AllArgsConstructor
public class YubikeyPivStrategy implements SshIdentityStrategy {
private String getFile() {
private String getFile(ShellControl sc) {
var file =
switch (OsType.ofLocal()) {
case OsType.Linux ignored -> "/usr/local/lib/libykcs11.so";
switch (sc.getOsType()) {
case OsType.MacOs ignored -> "/usr/local/lib/libykcs11.dylib";
case OsType.Windows ignored -> {
var x64 = "C:\\Program Files\\Yubico\\Yubico PIV Tool\\bin\\libykcs11.dll";
@@ -44,6 +43,7 @@ public class YubikeyPivStrategy implements SshIdentityStrategy {
yield x64;
}
default -> "/usr/local/lib/libykcs11.so";
};
return file;
}
@@ -52,7 +52,7 @@ public class YubikeyPivStrategy implements SshIdentityStrategy {
public void prepareParent(ShellControl parent) throws Exception {
parent.requireLicensedFeature(LicenseProvider.get().getFeature("pkcs11Identity"));
var file = getFile();
var file = getFile(parent);
if (!parent.getShellDialect().createFileExistsCommand(parent, file).executeAndCheck()) {
throw ErrorEventFactory.expected(new IOException("Yubikey PKCS11 library at " + file + " not found"));
}
@@ -61,7 +61,7 @@ public class YubikeyPivStrategy implements SshIdentityStrategy {
@Override
public void buildCommand(CommandBuilder builder) {
builder.setup(sc -> {
var file = getFile();
var file = getFile(sc);
var dir = FilePath.of(file).getParent();
if (sc.getOsType() == OsType.WINDOWS) {
builder.addToPath(dir, true);
@@ -72,10 +72,10 @@ public class YubikeyPivStrategy implements SshIdentityStrategy {
}
@Override
public List<KeyValue> configOptions() {
public List<KeyValue> configOptions(ShellControl sc) {
return List.of(
new KeyValue("IdentitiesOnly", "no"),
new KeyValue("PKCS11Provider", "\"" + getFile() + "\""),
new KeyValue("PKCS11Provider", "\"" + getFile(sc) + "\""),
new KeyValue("IdentityFile", "none"),
new KeyValue("IdentityAgent", "none"));
}