From 70e8f1e792a560cfba709cd90e4f549a268a70f3 Mon Sep 17 00:00:00 2001 From: Puru Yanamala Date: Fri, 3 May 2024 12:25:01 +0100 Subject: [PATCH] [GENE-2434] - Vault token read from file. --- lucene/default-nested-ivy-settings.xml | 2 +- .../solr/handler/dataimport/FileWatcher.java | 184 ++++++++++++++++++ .../handler/dataimport/VaultServiceImpl.java | 39 +++- 3 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/FileWatcher.java diff --git a/lucene/default-nested-ivy-settings.xml b/lucene/default-nested-ivy-settings.xml index c9fe95be0ce3..9961a4d33c4d 100644 --- a/lucene/default-nested-ivy-settings.xml +++ b/lucene/default-nested-ivy-settings.xml @@ -33,7 +33,7 @@ - + diff --git a/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/FileWatcher.java b/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/FileWatcher.java new file mode 100644 index 000000000000..c0ddec7d5acf --- /dev/null +++ b/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/FileWatcher.java @@ -0,0 +1,184 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (C) 2014 SilkCloud and/or its affiliates. All rights reserved. + */ + +package org.apache.solr.handler.dataimport; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import static java.nio.file.StandardWatchEventKinds.*; + +/** + * Java doc. + */ +public class FileWatcher { + //region singleton + private static volatile FileWatcher instance; + + public static FileWatcher getInstance() { + // lazy singleton + if (instance == null) { + synchronized (FileWatcher.class) { + if (instance == null) { + FileWatcher instance = new FileWatcher(); + instance.initialize(); + FileWatcher.instance = instance; + } + } + } + return instance; + } + + public static void setInstance(FileWatcher watcher) { + // setInstance for unit testing + instance = watcher; + } + //endregion + + //region private fields + private static Logger logger = LoggerFactory.getLogger(FileWatcher.class); + + private Thread thread; + private WatchService watchService; + private FileChangeListenerMap listeners = new FileChangeListenerMap(); + //endregion + + /**. + * File listener + */ + public interface FileListener { + void onFileChanged(Path path, WatchEvent.Kind kind); + } + + public FileWatcher addListener(Path path, FileListener listener) { + try { + WatchKey key = path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY); + listeners.addListener(key, listener); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + + return this; + } + + public void close() { + if (thread != null) { + thread.interrupt(); + thread = null; + } + } + + //region private methods + + // protected empty constructor for subclassing/mocking + protected FileWatcher() { + } + + private void initialize() { + try { + watchService = FileSystems.getDefault().newWatchService(); + } + catch (IOException ex) { + throw new RuntimeException("Error creating watch service.", ex); + } + thread = new Thread(new Runnable() { + @Override + public void run() { + FileWatcher.this.run(); + } + }); + thread.start(); + } + + private void run() { + for (; ; ) { + try { + WatchKey key = watchService.take(); + Path path = (Path) key.watchable(); + + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + + if (kind == StandardWatchEventKinds.OVERFLOW) { + logger.warn("Overflow happened in FileWatcher for key " + key); + continue; + } + + WatchEvent ev = (WatchEvent) event; + listeners.notify(key, path, ev.kind()); + } + } + catch (InterruptedException ex) { + return; + } + catch (Exception ex) { + logger.warn("Error occurred in file watcher. ", ex); + } + } + } + + //endregion + + //region private classes + + private static final class FileChangeListenerList { + private ArrayList> listeners = new ArrayList<>(); + + public void addListener(FileListener listener) { + listeners.add(new WeakReference<>(listener)); + } + + public void notify(Path path, WatchEvent.Kind kind) { + ArrayList> toRemove = new ArrayList<>(); + for (WeakReference listenerWeakReference : listeners) { + FileListener listener = listenerWeakReference.get(); + if (listener == null) { + toRemove.add(listenerWeakReference); + } + else { + try { + listener.onFileChanged(path, kind); + } + catch (Exception ex) { + logger.warn("Error occurred in file watcher. ", ex); + } + } + } + listeners.removeAll(toRemove); + } + } + + private static final class FileChangeListenerMap { + private ConcurrentHashMap listeners = new ConcurrentHashMap<>(); + + public void addListener(WatchKey watchKey, FileListener listener) { + FileChangeListenerList list = listeners.get(watchKey); + if (list == null) { + final FileChangeListenerList newList = new FileChangeListenerList(); + list = listeners.putIfAbsent(watchKey, newList); + if (list == null) { + list = newList; + } + } + list.addListener(listener); + } + + public void notify(WatchKey watchKey, Path path, WatchEvent.Kind kind) { + FileChangeListenerList list = listeners.get(watchKey); + if (list != null) { + list.notify(path, kind); + } + } + } + + //endregion +} diff --git a/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/VaultServiceImpl.java b/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/VaultServiceImpl.java index 9ee8264fcbb1..2a846178bd3d 100644 --- a/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/VaultServiceImpl.java +++ b/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/VaultServiceImpl.java @@ -6,25 +6,47 @@ import org.springframework.vault.support.VaultResponse; import java.net.URI; +import java.util.List; import java.util.Map; import java.util.Properties; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.io.*; public class VaultServiceImpl { private static final String ENV_VAULT_TOKEN = "VAULT_TOKEN"; + private static final String ENV_VAULT_AUTH_PATH = "VAULT_AUTH_PATH"; private static final String ENV_VAULT_ADDR = "VAULT_ADDR"; private static final String ENV_VAULT_PATH = "VAULT_PATH"; private static final String DATA = "data"; - + private static final String vaultTokenFilePath = System.getenv().getOrDefault(ENV_VAULT_AUTH_PATH, "/etc/vault/token"); private VaultTemplate vaultTemplate; + private String vaultToken; + private String vaultHost; public VaultServiceImpl() { try { - String vaultToken = System.getenv(ENV_VAULT_TOKEN); - String vaultHost = System.getenv(ENV_VAULT_ADDR); + vaultToken = getVaultToken(); + vaultHost = System.getenv(ENV_VAULT_ADDR); vaultTemplate = new VaultTemplate(VaultEndpoint.from(new URI(vaultHost)), new TokenAuthentication(vaultToken)); } catch (Exception e) { throw new RuntimeException("Failed to connect to vault using token", e); } + watchVaultFileUpdates(); + } + + private String getVaultToken() { + try { + List lines = Files.readAllLines(Paths.get(vaultTokenFilePath)); + if (!lines.isEmpty()) { + return lines.get(0); + } else { + throw new RuntimeException(vaultTokenFilePath + ": vault auth token file is empty."); + } + } catch (IOException e) { + throw new RuntimeException("Error reading vault auth token file", e); + } } public Properties readVaultProperties() { @@ -58,4 +80,15 @@ private static Properties readVaultProperties(VaultResponse vaultResponse) { return new Properties(); } + + private void watchVaultFileUpdates() { + FileWatcher.getInstance().addListener(Paths.get(vaultTokenFilePath).getParent(), (path, kind) -> { + vaultToken = getVaultToken(); + try { + vaultTemplate = new VaultTemplate(VaultEndpoint.from(new URI(vaultHost)), new TokenAuthentication(vaultToken)); + } catch (Exception e) { + throw new RuntimeException("vault configuration is wrong", e); + } + }); + } }