From 7e5fe7b84968d5d3303895058ec75a9ee3866333 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Thu, 18 Jul 2019 14:00:29 -0400 Subject: [PATCH 01/25] Move checking for resources in .gyro/init to Gyro.main This is so we can use RootScope to load any Gyro file. This will be used by the new Vault system to load/store secrets using the Gyro language format. --- cli/src/main/java/gyro/cli/Gyro.java | 5 +++++ core/src/main/java/gyro/core/scope/RootScope.java | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cli/src/main/java/gyro/cli/Gyro.java b/cli/src/main/java/gyro/cli/Gyro.java index 8a082523a..0181c69aa 100644 --- a/cli/src/main/java/gyro/cli/Gyro.java +++ b/cli/src/main/java/gyro/cli/Gyro.java @@ -6,6 +6,7 @@ import gyro.core.command.GyroCommand; import gyro.core.GyroCore; import gyro.core.GyroException; +import gyro.core.resource.Resource; import gyro.core.scope.Defer; import gyro.core.scope.RootScope; import ch.qos.logback.classic.Level; @@ -64,6 +65,10 @@ public static void main(String[] arguments) { init.evaluate(); + if (init.values().stream().anyMatch(Resource.class::isInstance)) { + throw new GyroException(String.format("Resources are not allowed in '%s'%n", GyroCore.INIT_FILE)); + } + } else { init = null; } diff --git a/core/src/main/java/gyro/core/scope/RootScope.java b/core/src/main/java/gyro/core/scope/RootScope.java index b169eb773..0e9088d80 100644 --- a/core/src/main/java/gyro/core/scope/RootScope.java +++ b/core/src/main/java/gyro/core/scope/RootScope.java @@ -266,10 +266,6 @@ public void evaluate() { private void validate() { StringBuilder sb = new StringBuilder(); - if (values().stream().anyMatch(Resource.class::isInstance)) { - sb.append(String.format("Resources are not allowed in '%s'%n", getFile())); - } - Map> duplicateResources = new HashMap<>(); for (Resource resource : findResources()) { String fullName = resource.primaryKey(); From 341d579ed56f7b271c1d913b1589eccbd929b2c8 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Thu, 18 Jul 2019 15:51:17 -0400 Subject: [PATCH 02/25] Initial implementation of Gyro vault functionality Implements: - Vault abstract class that custom vault implementations can subclass - LocalVault implementation that stores encrypted secrets in .gyro directory - VaultCommand for getting/setting and listing secrets - VaultReferenceResolver to provide a method to lookup secrets within Gyro configs - VaultDirectiveProcessor for configuring vault implementations in .gyro/init.gyro --- .../main/java/gyro/core/scope/RootScope.java | 12 +- .../main/java/gyro/core/vault/LocalVault.java | 359 ++++++++++++++++++ .../core/vault/LocalVaultSecretResource.java | 95 +++++ core/src/main/java/gyro/core/vault/Vault.java | 24 ++ .../java/gyro/core/vault/VaultCommand.java | 97 +++++ .../core/vault/VaultDirectiveProcessor.java | 57 +++ .../java/gyro/core/vault/VaultPlugin.java | 32 ++ .../core/vault/VaultReferenceResolver.java | 51 +++ .../java/gyro/core/vault/VaultSettings.java | 46 +++ 9 files changed, 770 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/gyro/core/vault/LocalVault.java create mode 100644 core/src/main/java/gyro/core/vault/LocalVaultSecretResource.java create mode 100644 core/src/main/java/gyro/core/vault/Vault.java create mode 100644 core/src/main/java/gyro/core/vault/VaultCommand.java create mode 100644 core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java create mode 100644 core/src/main/java/gyro/core/vault/VaultPlugin.java create mode 100644 core/src/main/java/gyro/core/vault/VaultReferenceResolver.java create mode 100644 core/src/main/java/gyro/core/vault/VaultSettings.java diff --git a/core/src/main/java/gyro/core/scope/RootScope.java b/core/src/main/java/gyro/core/scope/RootScope.java index 0e9088d80..338f2e86e 100644 --- a/core/src/main/java/gyro/core/scope/RootScope.java +++ b/core/src/main/java/gyro/core/scope/RootScope.java @@ -43,6 +43,9 @@ import gyro.core.resource.Resource; import gyro.core.resource.ResourcePlugin; import gyro.core.resource.TypeDescriptionDirectiveProcessor; +import gyro.core.vault.VaultDirectiveProcessor; +import gyro.core.vault.VaultPlugin; +import gyro.core.vault.VaultReferenceResolver; import gyro.core.virtual.VirtualDirectiveProcessor; import gyro.core.workflow.CreateDirectiveProcessor; import gyro.core.workflow.DeleteDirectiveProcessor; @@ -103,7 +106,8 @@ public RootScope(String file, FileBackend backend, RootScope current, Set getSettings(PluginSettings.class).getPlugins().add(p)); Stream.of( @@ -122,11 +126,13 @@ public RootScope(String file, FileBackend backend, RootScope current, Set getSettings(DirectiveSettings.class).getProcessors().put(p.getName(), p)); Stream.of( - new FinderReferenceResolver()) + new FinderReferenceResolver(), + new VaultReferenceResolver()) .forEach(r -> getSettings(ReferenceSettings.class).getResolvers().put(r.getName(), r)); put("ENV", System.getenv()); diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java new file mode 100644 index 000000000..68479f752 --- /dev/null +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -0,0 +1,359 @@ +package gyro.core.vault; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import gyro.core.GyroCore; +import gyro.core.GyroException; +import gyro.core.LocalFileBackend; +import gyro.core.Type; +import gyro.core.resource.Diffable; +import gyro.core.resource.DiffableField; +import gyro.core.resource.DiffableInternals; +import gyro.core.resource.DiffableType; +import gyro.core.resource.Resource; +import gyro.core.scope.RootScope; +import gyro.lang.ast.Node; +import gyro.lang.ast.NodePrinter; +import gyro.lang.ast.PairNode; +import gyro.lang.ast.PrinterContext; +import gyro.lang.ast.block.KeyBlockNode; +import gyro.lang.ast.block.ResourceNode; +import gyro.lang.ast.value.ListNode; +import gyro.lang.ast.value.MapNode; +import gyro.lang.ast.value.ReferenceNode; +import gyro.lang.ast.value.ValueNode; +import gyro.util.Bug; +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.security.SecureRandom; +import java.security.spec.KeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; + +@Type("local") +public class LocalVault extends Vault { + + private String vaultFile = ".gyro/vault.gyro"; + private String keyPath; + private String cipher; + + private static final String ALGORITHM = "AES"; + + public String getVaultFile() { + if (!vaultFile.startsWith(".gyro")) { + return ".gyro" + File.separator + vaultFile; + } + + return vaultFile; + } + + public void setVaultFile(String vaultFile) { + this.vaultFile = vaultFile; + } + + public String getKeyPath() { + return keyPath; + } + + public void setKeyPath(String keyPath) { + this.keyPath = keyPath; + } + + public String getCipher() { + return cipher; + } + + public void setCipher(String cipher) { + this.cipher = cipher; + } + + public String key() { + return "foobar"; + } + + @Override + public String get(String key) { + RootScope vaultScope = loadVault(); + DiffableType type = DiffableType.getInstance(LocalVaultSecretResource.class); + LocalVaultSecretResource secret = (LocalVaultSecretResource) vaultScope.findResource(type.getName() + "::" + key); + + if (secret != null) { + return decrypt(secret); + } + + return null; + } + + @Override + public boolean put(String key, String value) { + RootScope vaultScope = loadVault(); + DiffableType type = DiffableType.getInstance(LocalVaultSecretResource.class); + LocalVaultSecretResource secret = encrypt(value); + DiffableInternals.setName(secret, key); + + String name = type.getName() + "::" + key; + boolean overwritten = vaultScope.containsKey(name); + + vaultScope.put(name, secret); + save(vaultScope); + + return overwritten; + } + + @Override + public Map list(String prefix) { + Map secrets = new HashMap<>(); + + RootScope vaultScope = loadVault(); + vaultScope.findResourcesByClass(LocalVaultSecretResource.class) + .filter(s -> prefix == null || DiffableInternals.getName(s).startsWith(prefix)) + .forEach(s -> secrets.put(DiffableInternals.getName(s), decrypt(s))); + + return secrets; + } + + private RootScope loadVault() { + Path vaultPath = Paths.get(getVaultFile()); + if (!Files.exists(vaultPath)) { + try { + Files.createFile(vaultPath, PosixFilePermissions.asFileAttribute(ImmutableSet.of(OWNER_READ, OWNER_WRITE))); + } catch (Exception ex) { + throw new GyroException(ex); + } + } + + RootScope vault = new RootScope( + getVaultFile(), + new LocalFileBackend(GyroCore.getRootDirectory()), + null, + ImmutableSet.of()); + + vault.getRootScope().put(DiffableType.getInstance(LocalVaultSecretResource.class).getName(), LocalVaultSecretResource.class); + + try { + vault.evaluate(); + } catch (GyroException ex) { + if (!(ex.getCause() instanceof NoSuchFileException)) { + throw ex; + } + } + + return vault; + } + + public void save(RootScope vaultScope) { + NodePrinter printer = new NodePrinter(); + + try (PrintWriter out = new PrintWriter( + new OutputStreamWriter( + vaultScope.openOutput(getVaultFile()), + StandardCharsets.UTF_8))) { + + PrinterContext context = new PrinterContext(out, 0); + + for (Object value : vaultScope.values()) { + if (value instanceof Resource) { + Resource resource = (Resource) value; + + printer.visit( + new ResourceNode( + DiffableType.getInstance(resource.getClass()).getName(), + new ValueNode(DiffableInternals.getName(resource)), + toBodyNodes(resource)), + context); + } + } + + } catch (IOException error) { + throw new Bug(error); + } + } + + + private List toBodyNodes(Diffable diffable) { + List body = new ArrayList<>(); + + for (DiffableField field : DiffableType.getInstance(diffable.getClass()).getFields()) { + Object value = field.getValue(diffable); + + if (value == null) { + continue; + } + + String key = field.getName(); + + if (value instanceof Boolean + || value instanceof Map + || value instanceof Number + || value instanceof String) { + + body.add(toPairNode(key, value)); + + } else if (value instanceof Date) { + body.add(toPairNode(key, value.toString())); + + } else if (value instanceof Enum) { + body.add(toPairNode(key, ((Enum) value).name())); + + } else if (value instanceof Diffable) { + if (field.shouldBeDiffed()) { + body.add(new KeyBlockNode(key, null, toBodyNodes((Diffable) value))); + + } else { + body.add(toPairNode(key, value)); + } + + } else if (value instanceof Collection) { + if (field.shouldBeDiffed()) { + for (Object item : (Collection) value) { + body.add(new KeyBlockNode(key, null, toBodyNodes((Diffable) item))); + } + + } else { + body.add(toPairNode(key, value)); + } + + } else { + throw new GyroException(String.format( + "Can't convert @|bold %s|@, an instance of @|bold %s|@, into a node!", + value, + value.getClass().getName())); + } + } + + return body; + } + + private PairNode toPairNode(Object key, Object value) { + return new PairNode(toNode(key), toNode(value)); + } + + private Node toNode(Object value) { + if (value instanceof Boolean + || value instanceof Number + || value instanceof String) { + + return new ValueNode(value); + + } else if (value instanceof Collection) { + List items = new ArrayList<>(); + + for (Object item : (Collection) value) { + if (item != null) { + items.add(toNode(item)); + } + } + + return new ListNode(items); + + } else if (value instanceof Map) { + List entries = new ArrayList<>(); + + for (Map.Entry entry : ((Map) value).entrySet()) { + Object v = entry.getValue(); + + if (v != null) { + entries.add(toPairNode(entry.getKey(), v)); + } + } + + return new MapNode(entries); + + } else if (value instanceof Resource) { + Resource resource = (Resource) value; + DiffableType type = DiffableType.getInstance(resource.getClass()); + + if (DiffableInternals.isExternal(resource)) { + return new ValueNode(type.getIdField().getValue(resource)); + + } else { + return new ReferenceNode( + Arrays.asList( + new ValueNode(type.getName()), + new ValueNode(DiffableInternals.getName(resource))), + Collections.emptyList()); + } + + } else { + throw new GyroException(String.format( + "Can't convert @|bold %s|@, an instance of @|bold %s|@, into a node!", + value, + value.getClass().getName())); + } + } + + private String decrypt(LocalVaultSecretResource secret) { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(key().toCharArray(), secret.getSaltAsBytes(), 10000, 128); + + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES"); + + IvParameterSpec ivspec = new IvParameterSpec(secret.getIvAsBytes()); + + Cipher cipher = Cipher.getInstance(getCipher()); + cipher.init(Cipher.DECRYPT_MODE, skey, ivspec); + + byte[] decryptedBytes = cipher.doFinal(secret.getEncryptedDataAsBytes()); + + return new String(decryptedBytes, StandardCharsets.UTF_8); + } catch (Exception ex) { + throw new GyroException("Error decrypting vault data: ", ex); + } + } + + private LocalVaultSecretResource encrypt(String plaintext) { + try { + byte[] salt = new byte[8]; + SecureRandom.getInstanceStrong().nextBytes(salt); + + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(key().toCharArray(), salt, 10000, 128); + + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES"); + + byte[] iv = new byte[128/8]; + SecureRandom.getInstanceStrong().nextBytes(iv); + IvParameterSpec ivspec = new IvParameterSpec(iv); + + Cipher cipher = Cipher.getInstance(getCipher()); + cipher.init(Cipher.ENCRYPT_MODE, skey, ivspec); + + byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); + + return new LocalVaultSecretResource(iv, salt, getCipher(), encryptedBytes); + } catch (Exception ex) { + throw new GyroException("Error encrypting vault data: ", ex); + } + } + +} diff --git a/core/src/main/java/gyro/core/vault/LocalVaultSecretResource.java b/core/src/main/java/gyro/core/vault/LocalVaultSecretResource.java new file mode 100644 index 000000000..7977690c5 --- /dev/null +++ b/core/src/main/java/gyro/core/vault/LocalVaultSecretResource.java @@ -0,0 +1,95 @@ +package gyro.core.vault; + +import gyro.core.GyroUI; +import gyro.core.Namespace; +import gyro.core.Type; +import gyro.core.resource.Resource; +import gyro.core.scope.State; +import org.apache.commons.codec.binary.Base64; + +import java.util.Set; + +@Type("vault-secret") +@Namespace("gyro") +public class LocalVaultSecretResource extends Resource { + + private String iv; + private String salt; + private String cipher; + private String encryptedData; + + public LocalVaultSecretResource() { + } + + public LocalVaultSecretResource(byte[] iv, byte[] salt, String cipher, byte[] encryptedData) { + this.iv = Base64.encodeBase64String(iv); + this.salt = Base64.encodeBase64String(salt); + this.cipher = cipher; + this.encryptedData = Base64.encodeBase64String(encryptedData); + } + + public String getIv() { + return iv; + } + + public byte[] getIvAsBytes() { + return Base64.decodeBase64(getIv()); + } + + public void setIv(String iv) { + this.iv = iv; + } + + public String getSalt() { + return salt; + } + + public byte[] getSaltAsBytes() { + return Base64.decodeBase64(getSalt()); + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public String getCipher() { + return cipher; + } + + public void setCipher(String cipher) { + this.cipher = cipher; + } + + public String getEncryptedData() { + return encryptedData; + } + + public byte[] getEncryptedDataAsBytes() { + return Base64.decodeBase64(getEncryptedData()); + } + + public void setEncryptedData(String encryptedData) { + this.encryptedData = encryptedData; + } + + @Override + public boolean refresh() { + return false; + } + + @Override + public void create(GyroUI ui, State state) throws Exception { + + } + + @Override + public void update(GyroUI ui, State state, Resource current, Set changedFieldNames) throws Exception { + + } + + @Override + public void delete(GyroUI ui, State state) throws Exception { + + } + +} diff --git a/core/src/main/java/gyro/core/vault/Vault.java b/core/src/main/java/gyro/core/vault/Vault.java new file mode 100644 index 000000000..929b9725f --- /dev/null +++ b/core/src/main/java/gyro/core/vault/Vault.java @@ -0,0 +1,24 @@ +package gyro.core.vault; + +import java.util.List; +import java.util.Map; + +public abstract class Vault { + + private String name; + + public abstract String get(String key); + + public abstract boolean put(String key, String value); + + public abstract Map list(String filter); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/core/src/main/java/gyro/core/vault/VaultCommand.java b/core/src/main/java/gyro/core/vault/VaultCommand.java new file mode 100644 index 000000000..f3a32fd79 --- /dev/null +++ b/core/src/main/java/gyro/core/vault/VaultCommand.java @@ -0,0 +1,97 @@ +package gyro.core.vault; + +import gyro.core.GyroCore; +import gyro.core.GyroException; +import gyro.core.command.AbstractCommand; +import gyro.core.command.GyroCommand; +import gyro.core.scope.RootScope; +import io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import io.airlift.airline.Option; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Command(name = "vault", description = "Store/retrieve secrets") +public class VaultCommand extends AbstractCommand { + + @Arguments + private List arguments = new ArrayList<>(); + + @Option(name = { "--vault" }, description = "The vault name to manipulate or query.") + private String vaultName = "default"; + + public List getArguments() { + return arguments; + } + + public void setArguments(List arguments) { + this.arguments = arguments; + } + + public String getVaultName() { + return vaultName; + } + + public void setVaultName(String vaultName) { + this.vaultName = vaultName; + } + + @Override + protected void doExecute() throws Exception { + if (getArguments().isEmpty()) { + throw new GyroException("Expected 'gyro vault get|put key' or 'gyro vault list'"); + } + + RootScope scope = (RootScope) getInit(); + VaultSettings settings = scope.getSettings(VaultSettings.class); + Vault vault = settings.getVaultsByName().get(getVaultName()); + + if (vault == null) { + throw new GyroException("Unable to load the vault named '" + getVaultName() + "'. Ensure the vault is configured in .gyro/init.gyro."); + } + + String command = getArguments().get(0); + + if ("get".equalsIgnoreCase(command)) { + String key = getArguments().size() >= 2 ? getArguments().get(1) : null; + + if (key == null) { + throw new GyroException("Key argument missing. Expected 'gyro vault get '"); + } + + String secret = vault.get(key); + if (secret == null) { + GyroCore.ui().write(""); + } else { + GyroCore.ui().write(vault.get(key) + "\n"); + } + } else if ("put".equalsIgnoreCase(command)) { + String key = getArguments().size() >= 2 ? getArguments().get(1) : null; + String value = getArguments().size() >= 3 ? getArguments().get(2) : null; + + if (key == null || value == null) { + throw new GyroException("Key or value argument missing. Expected 'gyro vault put '"); + } + + if (vault.put(key, value)) { + GyroCore.ui().write("\nKey '%s' was overwritten in the '%s' vault.\n", key, getVaultName()); + } else { + GyroCore.ui().write("\nKey '%s' was written to the '%s' vault.\n", key, getVaultName()); + } + } else if ("list".equalsIgnoreCase(command)) { + String prefix = getArguments().size() == 2 ? getArguments().get(1) : null; + + GyroCore.ui().write("\n"); + Map secrets = vault.list(prefix); + for (String key : secrets.keySet()) { + GyroCore.ui().write("%s: %s\n", key, secrets.get(key)); + } + } else { + throw new GyroException("Unknown command. Valid commands are: get and put"); + } + + } + +} diff --git a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java new file mode 100644 index 000000000..bacd5e554 --- /dev/null +++ b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java @@ -0,0 +1,57 @@ +package gyro.core.vault; + +import com.google.common.base.CaseFormat; +import gyro.core.GyroException; +import gyro.core.Reflections; +import gyro.core.directive.DirectiveProcessor; +import gyro.core.scope.RootScope; +import gyro.core.scope.Scope; +import gyro.lang.ast.block.DirectiveNode; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.List; + +public class VaultDirectiveProcessor extends DirectiveProcessor { + + @Override + public String getName() { + return "vault"; + } + + @Override + public void process(RootScope scope, DirectiveNode node) throws Exception { + List arguments = evaluateDirectiveArguments(scope, node, 1, 2); + String type = (String) arguments.get(0); + String name = arguments.size() == 1 ? "default" : (String) arguments.get(1); + Scope bodyScope = evaluateBody(scope, node); + + VaultSettings settings = scope.getSettings(VaultSettings.class); + Class vaultClass = settings.getVaultClasses().get(type); + + if (vaultClass == null) { + throw new GyroException("Unable to find a vault implementation named '" + type + "'."); + } + + Vault vault = Reflections.newInstance(vaultClass); + vault.setName(name); + + for (PropertyDescriptor property : Reflections.getBeanInfo(vaultClass).getPropertyDescriptors()) { + Method setter = property.getWriteMethod(); + + if (setter != null && !"setName".equals(setter.getName())) { + Reflections.invoke(setter, vault, scope.convertValue( + setter.getGenericParameterTypes()[0], + bodyScope.get(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, property.getName())))); + } + } + + if (settings.getVaultsByName().containsKey(name)) { + throw new GyroException("A vault with the name '" + name + "' was previously defined."); + } + + settings.getVaultsByName().put(name, vault); + } + + +} diff --git a/core/src/main/java/gyro/core/vault/VaultPlugin.java b/core/src/main/java/gyro/core/vault/VaultPlugin.java new file mode 100644 index 000000000..0d431ab60 --- /dev/null +++ b/core/src/main/java/gyro/core/vault/VaultPlugin.java @@ -0,0 +1,32 @@ +package gyro.core.vault; + +import gyro.core.GyroException; +import gyro.core.Type; +import gyro.core.plugin.Plugin; +import gyro.core.scope.RootScope; + +import java.util.Optional; + +public class VaultPlugin extends Plugin { + + @Override + public void onEachClass(RootScope root, Class aClass) throws Exception { + if (Vault.class.isAssignableFrom(aClass)) { + @SuppressWarnings("unchecked") + Class vaultClass = (Class) aClass; + + String type = Optional.ofNullable(vaultClass.getAnnotation(Type.class)) + .map(Type::value) + .orElse(null); + + if (type != null) { + root.getSettings(VaultSettings.class) + .getVaultClasses() + .put(type, vaultClass); + } else { + throw new GyroException("Loading vault plugin failed. Vault implementation is missing @Type annotation."); + } + } + } + +} diff --git a/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java b/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java new file mode 100644 index 000000000..0fce3aea1 --- /dev/null +++ b/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java @@ -0,0 +1,51 @@ +package gyro.core.vault; + +import gyro.core.GyroException; +import gyro.core.reference.ReferenceResolver; +import gyro.core.scope.RootScope; +import gyro.core.scope.Scope; + +import java.util.List; + +/** + * Examples: + * + * $(vault-lookup secret-key) - Grab key from the 'default' vault + * $(vault-lookup secret-key vault) - Grab key from the vault provided by vault + */ +public class VaultReferenceResolver extends ReferenceResolver { + + @Override + public String getName() { + return "vault-lookup"; + } + + @Override + public Object resolve(Scope scope, List arguments) throws Exception { + + if (arguments.isEmpty()) { + throw new GyroException("Missing key parameter. Expected $(vault-lookup )."); + } + + String key = null; + String vaultName = "default"; + + if (arguments.size() == 1) { + key = (String) arguments.get(0); + } else if (arguments.size() == 2) { + key = (String) arguments.get(0); + vaultName = (String) arguments.get(1); + } + + RootScope rootScope = scope.getRootScope(); + VaultSettings settings = rootScope.getSettings(VaultSettings.class); + Vault vault = settings.getVaultsByName().get(vaultName); + + if (vault == null) { + throw new GyroException("Unable to load the vault named '" + vaultName + "'. Ensure the vault is configured in .gyro/init.gyro."); + } + + return vault.get(key); + } + +} diff --git a/core/src/main/java/gyro/core/vault/VaultSettings.java b/core/src/main/java/gyro/core/vault/VaultSettings.java new file mode 100644 index 000000000..23ce873f1 --- /dev/null +++ b/core/src/main/java/gyro/core/vault/VaultSettings.java @@ -0,0 +1,46 @@ +package gyro.core.vault; + +import gyro.core.scope.Settings; + +import java.util.HashMap; +import java.util.Map; + +public class VaultSettings extends Settings { + + private Map> vaultClasses; + private Map vaultsByName; + + /** + * A map of vault implementations. The key is the name of the vault + * provided by the vault implementation's @Type annotation. + */ + public Map> getVaultClasses() { + if (vaultClasses == null) { + vaultClasses = new HashMap<>(); + vaultClasses.put("local", LocalVault.class); + } + + return vaultClasses; + } + + public void setVaultClasses(Map> vaultClasses) { + this.vaultClasses = vaultClasses; + } + + /** + * A map of vaults loaded using the @vault command. The key is the name of the vault + * provided to the @vault command, or 'default' if one wasn't provided. + */ + public Map getVaultsByName() { + if (vaultsByName == null) { + vaultsByName = new HashMap<>(); + } + + return vaultsByName; + } + + public void setVaultsByName(Map vaultsByName) { + this.vaultsByName = vaultsByName; + } + +} From 8d7a8a13cd60f93466db2afe4af4e1906689e5a9 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Thu, 18 Jul 2019 15:52:52 -0400 Subject: [PATCH 03/25] Make save method private, only used internally --- core/src/main/java/gyro/core/vault/LocalVault.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index 68479f752..51435c607 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -168,7 +168,7 @@ private RootScope loadVault() { return vault; } - public void save(RootScope vaultScope) { + private void save(RootScope vaultScope) { NodePrinter printer = new NodePrinter(); try (PrintWriter out = new PrintWriter( From 07820a7df6bfb892b1ca866bbf89d3c5d56846e5 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Thu, 18 Jul 2019 16:03:26 -0400 Subject: [PATCH 04/25] Cleanup --- core/src/main/java/gyro/core/vault/LocalVault.java | 5 ----- core/src/main/java/gyro/core/vault/Vault.java | 1 - core/src/main/java/gyro/core/vault/VaultCommand.java | 1 - 3 files changed, 7 deletions(-) diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index 51435c607..69bd91413 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -1,6 +1,5 @@ package gyro.core.vault; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import gyro.core.GyroCore; import gyro.core.GyroException; @@ -23,7 +22,6 @@ import gyro.lang.ast.value.ReferenceNode; import gyro.lang.ast.value.ValueNode; import gyro.util.Bug; -import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -40,8 +38,6 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.attribute.PosixFileAttributes; -import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.security.SecureRandom; import java.security.spec.KeySpec; @@ -196,7 +192,6 @@ private void save(RootScope vaultScope) { } } - private List toBodyNodes(Diffable diffable) { List body = new ArrayList<>(); diff --git a/core/src/main/java/gyro/core/vault/Vault.java b/core/src/main/java/gyro/core/vault/Vault.java index 929b9725f..328da5369 100644 --- a/core/src/main/java/gyro/core/vault/Vault.java +++ b/core/src/main/java/gyro/core/vault/Vault.java @@ -1,6 +1,5 @@ package gyro.core.vault; -import java.util.List; import java.util.Map; public abstract class Vault { diff --git a/core/src/main/java/gyro/core/vault/VaultCommand.java b/core/src/main/java/gyro/core/vault/VaultCommand.java index f3a32fd79..67c1d9026 100644 --- a/core/src/main/java/gyro/core/vault/VaultCommand.java +++ b/core/src/main/java/gyro/core/vault/VaultCommand.java @@ -3,7 +3,6 @@ import gyro.core.GyroCore; import gyro.core.GyroException; import gyro.core.command.AbstractCommand; -import gyro.core.command.GyroCommand; import gyro.core.scope.RootScope; import io.airlift.airline.Arguments; import io.airlift.airline.Command; From 3042f1d8bd28933e6fb40c39469751c4a694e772 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Fri, 19 Jul 2019 07:45:31 -0400 Subject: [PATCH 05/25] Read encryption key from key-path --- .../main/java/gyro/core/vault/LocalVault.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index 69bd91413..5e11e70a9 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -1,6 +1,7 @@ package gyro.core.vault; import com.google.common.collect.ImmutableSet; +import com.psddev.dari.util.IoUtils; import gyro.core.GyroCore; import gyro.core.GyroException; import gyro.core.LocalFileBackend; @@ -22,6 +23,7 @@ import gyro.lang.ast.value.ReferenceNode; import gyro.lang.ast.value.ValueNode; import gyro.util.Bug; +import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -30,6 +32,7 @@ import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; @@ -90,8 +93,15 @@ public void setCipher(String cipher) { this.cipher = cipher; } - public String key() { - return "foobar"; + public char[] key() { + try { + byte[] key = Base64.decodeBase64(IoUtils.toByteArray(new File(getKeyPath()))); + return new String(key, StandardCharsets.UTF_8).toCharArray(); + } catch (FileNotFoundException ex) { + throw new GyroException("Encryption key for '" + getName() + "' vault not found at path '" + getKeyPath() + "'"); + } catch (IOException ex) { + throw new GyroException("Unable to load encryption key for '" + getName() + "' vault.", ex); + } } @Override @@ -307,7 +317,7 @@ private Node toNode(Object value) { private String decrypt(LocalVaultSecretResource secret) { try { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); - KeySpec spec = new PBEKeySpec(key().toCharArray(), secret.getSaltAsBytes(), 10000, 128); + KeySpec spec = new PBEKeySpec(key(), secret.getSaltAsBytes(), 10000, 128); SecretKey tmp = factory.generateSecret(spec); SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES"); @@ -331,7 +341,7 @@ private LocalVaultSecretResource encrypt(String plaintext) { SecureRandom.getInstanceStrong().nextBytes(salt); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); - KeySpec spec = new PBEKeySpec(key().toCharArray(), salt, 10000, 128); + KeySpec spec = new PBEKeySpec(key(), salt, 10000, 128); SecretKey tmp = factory.generateSecret(spec); SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES"); From bbcf966974aacc6b5a4820e92743feb931b19b26 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Fri, 19 Jul 2019 07:52:59 -0400 Subject: [PATCH 06/25] Build vault file based on vault name rather than letting user set it --- .../main/java/gyro/core/vault/LocalVault.java | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index 5e11e70a9..c1577ee9d 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -59,24 +59,11 @@ @Type("local") public class LocalVault extends Vault { - private String vaultFile = ".gyro/vault.gyro"; private String keyPath; private String cipher; private static final String ALGORITHM = "AES"; - public String getVaultFile() { - if (!vaultFile.startsWith(".gyro")) { - return ".gyro" + File.separator + vaultFile; - } - - return vaultFile; - } - - public void setVaultFile(String vaultFile) { - this.vaultFile = vaultFile; - } - public String getKeyPath() { return keyPath; } @@ -93,6 +80,10 @@ public void setCipher(String cipher) { this.cipher = cipher; } + public Path vaultPath() { + return GyroCore.getRootDirectory().resolve(Paths.get(".gyro", "vault", getName())); + } + public char[] key() { try { byte[] key = Base64.decodeBase64(IoUtils.toByteArray(new File(getKeyPath()))); @@ -146,17 +137,17 @@ public Map list(String prefix) { } private RootScope loadVault() { - Path vaultPath = Paths.get(getVaultFile()); - if (!Files.exists(vaultPath)) { + if (!Files.exists(vaultPath())) { try { - Files.createFile(vaultPath, PosixFilePermissions.asFileAttribute(ImmutableSet.of(OWNER_READ, OWNER_WRITE))); + Files.createDirectories(vaultPath().getParent()); + Files.createFile(vaultPath(), PosixFilePermissions.asFileAttribute(ImmutableSet.of(OWNER_READ, OWNER_WRITE))); } catch (Exception ex) { throw new GyroException(ex); } } RootScope vault = new RootScope( - getVaultFile(), + vaultPath().toString(), new LocalFileBackend(GyroCore.getRootDirectory()), null, ImmutableSet.of()); @@ -179,7 +170,7 @@ private void save(RootScope vaultScope) { try (PrintWriter out = new PrintWriter( new OutputStreamWriter( - vaultScope.openOutput(getVaultFile()), + vaultScope.openOutput(vaultPath().toString()), StandardCharsets.UTF_8))) { PrinterContext context = new PrinterContext(out, 0); From c932c3f95a4fc2bb4bd04dcfe8ffe79ff5aa790d Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Fri, 19 Jul 2019 07:53:29 -0400 Subject: [PATCH 07/25] Remove unused ALGORITHM field --- core/src/main/java/gyro/core/vault/LocalVault.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index c1577ee9d..b0b973e50 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -62,8 +62,6 @@ public class LocalVault extends Vault { private String keyPath; private String cipher; - private static final String ALGORITHM = "AES"; - public String getKeyPath() { return keyPath; } From cdadb32b2fa3bfe34cc78f174d7bd52cd36ad2cd Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Fri, 19 Jul 2019 08:11:59 -0400 Subject: [PATCH 08/25] Allow custom key length and key iterations --- .../main/java/gyro/core/vault/LocalVault.java | 35 +++++++++++++++++-- .../core/vault/VaultDirectiveProcessor.java | 10 ++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index b0b973e50..14f16e432 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -51,6 +51,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; @@ -61,6 +62,8 @@ public class LocalVault extends Vault { private String keyPath; private String cipher; + private Integer keyLength; + private Integer keyIterations; public String getKeyPath() { return keyPath; @@ -78,6 +81,34 @@ public void setCipher(String cipher) { this.cipher = cipher; } + public Integer getKeyLength() { + if (keyLength == null) { + return 256; + } + + return keyLength; + } + + public void setKeyLength(Integer keyLength) { + this.keyLength = keyLength; + } + + public Integer getKeyIterations() { + if (keyIterations == null) { + return 65536; + } + + return keyIterations; + } + + public void setKeyIterations(Integer keyIterations) { + if (keyIterations != null && keyIterations < 10000) { + throw new GyroException(String.format(Locale.getDefault(), "Key iterations must be greater than %,d per NIST recommended guidelines.", 10000)); + } + + this.keyIterations = keyIterations; + } + public Path vaultPath() { return GyroCore.getRootDirectory().resolve(Paths.get(".gyro", "vault", getName())); } @@ -306,7 +337,7 @@ private Node toNode(Object value) { private String decrypt(LocalVaultSecretResource secret) { try { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); - KeySpec spec = new PBEKeySpec(key(), secret.getSaltAsBytes(), 10000, 128); + KeySpec spec = new PBEKeySpec(key(), secret.getSaltAsBytes(), getKeyIterations(), getKeyLength()); SecretKey tmp = factory.generateSecret(spec); SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES"); @@ -330,7 +361,7 @@ private LocalVaultSecretResource encrypt(String plaintext) { SecureRandom.getInstanceStrong().nextBytes(salt); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); - KeySpec spec = new PBEKeySpec(key(), salt, 10000, 128); + KeySpec spec = new PBEKeySpec(key(), salt, getKeyIterations(), getKeyLength()); SecretKey tmp = factory.generateSecret(spec); SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES"); diff --git a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java index bacd5e554..8391db9f4 100644 --- a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java +++ b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java @@ -40,9 +40,13 @@ public void process(RootScope scope, DirectiveNode node) throws Exception { Method setter = property.getWriteMethod(); if (setter != null && !"setName".equals(setter.getName())) { - Reflections.invoke(setter, vault, scope.convertValue( - setter.getGenericParameterTypes()[0], - bodyScope.get(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, property.getName())))); + Object value = scope.convertValue( + setter.getGenericParameterTypes()[0], + bodyScope.get(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, property.getName()))); + + if (value != null) { + Reflections.invoke(setter, vault, value); + } } } From eab7268622b3c34ce91e18a605915bb99356a727 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Fri, 19 Jul 2019 08:16:27 -0400 Subject: [PATCH 09/25] Add ability to remove a secret from the vault --- core/src/main/java/gyro/core/vault/LocalVault.java | 10 ++++++++++ core/src/main/java/gyro/core/vault/Vault.java | 2 ++ core/src/main/java/gyro/core/vault/VaultCommand.java | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index 14f16e432..3a412633e 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -153,6 +153,16 @@ public boolean put(String key, String value) { return overwritten; } + @Override + public void remove(String key) { + RootScope vaultScope = loadVault(); + DiffableType type = DiffableType.getInstance(LocalVaultSecretResource.class); + + vaultScope.remove(type.getName() + "::" + key); + + save(vaultScope); + } + @Override public Map list(String prefix) { Map secrets = new HashMap<>(); diff --git a/core/src/main/java/gyro/core/vault/Vault.java b/core/src/main/java/gyro/core/vault/Vault.java index 328da5369..f6be29911 100644 --- a/core/src/main/java/gyro/core/vault/Vault.java +++ b/core/src/main/java/gyro/core/vault/Vault.java @@ -10,6 +10,8 @@ public abstract class Vault { public abstract boolean put(String key, String value); + public abstract void remove(String key); + public abstract Map list(String filter); public String getName() { diff --git a/core/src/main/java/gyro/core/vault/VaultCommand.java b/core/src/main/java/gyro/core/vault/VaultCommand.java index 67c1d9026..08a520038 100644 --- a/core/src/main/java/gyro/core/vault/VaultCommand.java +++ b/core/src/main/java/gyro/core/vault/VaultCommand.java @@ -66,6 +66,16 @@ protected void doExecute() throws Exception { } else { GyroCore.ui().write(vault.get(key) + "\n"); } + } else if ("remove".equalsIgnoreCase(command) || "rm".equalsIgnoreCase(command)) { + String key = getArguments().size() >= 2 ? getArguments().get(1) : null; + + if (key == null) { + throw new GyroException("Key argument missing. Expected 'gyro vault get '"); + } + + vault.remove(key); + + GyroCore.ui().write("\nKey '%s' was removed in the '%s' vault.\n", key, getVaultName()); } else if ("put".equalsIgnoreCase(command)) { String key = getArguments().size() >= 2 ? getArguments().get(1) : null; String value = getArguments().size() >= 3 ? getArguments().get(2) : null; From 358e3d5afcaf99c0108d327cce2f9135061d97b6 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Fri, 19 Jul 2019 12:02:37 -0400 Subject: [PATCH 10/25] Initial work to implement CustomValue This work is intended to allow a custom value that can alter how it's serialized to state. For VaultSecret it'll write "$(vault-lookup key vault hash)" in state. --- .../java/gyro/core/resource/CustomValue.java | 9 +++ .../gyro/core/resource/DiffableField.java | 7 ++ core/src/main/java/gyro/core/scope/State.java | 5 +- .../core/vault/VaultReferenceResolver.java | 2 +- .../java/gyro/core/vault/VaultSecret.java | 76 +++++++++++++++++++ 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/gyro/core/resource/CustomValue.java create mode 100644 core/src/main/java/gyro/core/vault/VaultSecret.java diff --git a/core/src/main/java/gyro/core/resource/CustomValue.java b/core/src/main/java/gyro/core/resource/CustomValue.java new file mode 100644 index 000000000..ac69c2da3 --- /dev/null +++ b/core/src/main/java/gyro/core/resource/CustomValue.java @@ -0,0 +1,9 @@ +package gyro.core.resource; + +import gyro.lang.ast.Node; + +public interface CustomValue { + + public Node toStateNode(); + +} diff --git a/core/src/main/java/gyro/core/resource/DiffableField.java b/core/src/main/java/gyro/core/resource/DiffableField.java index 68c00863a..1ce65d346 100644 --- a/core/src/main/java/gyro/core/resource/DiffableField.java +++ b/core/src/main/java/gyro/core/resource/DiffableField.java @@ -4,6 +4,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; +import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -12,6 +13,7 @@ import com.psddev.dari.util.ObjectUtils; import gyro.core.GyroException; import gyro.core.Reflections; +import gyro.core.scope.DiffableScope; import gyro.core.scope.Scope; public class DiffableField { @@ -95,6 +97,11 @@ public boolean shouldBeDiffed() { } public Object getValue(Diffable diffable) { + DiffableScope scope = DiffableInternals.getScope(diffable); + if (scope != null && scope.get(getName()) instanceof CustomValue) { + return scope.get(getName()); + } + return Reflections.invoke(getter, diffable); } diff --git a/core/src/main/java/gyro/core/scope/State.java b/core/src/main/java/gyro/core/scope/State.java index 06fa575c1..91cc83696 100644 --- a/core/src/main/java/gyro/core/scope/State.java +++ b/core/src/main/java/gyro/core/scope/State.java @@ -22,6 +22,7 @@ import gyro.core.diff.Change; import gyro.core.diff.Delete; import gyro.core.diff.Replace; +import gyro.core.resource.CustomValue; import gyro.core.resource.Diffable; import gyro.core.resource.DiffableField; import gyro.core.resource.DiffableInternals; @@ -220,7 +221,9 @@ private List toBodyNodes(Diffable diffable) { String key = field.getName(); - if (value instanceof Boolean + if (value instanceof CustomValue) { + body.add(toPairNode(key, ((CustomValue) value).toStateNode())); + } else if (value instanceof Boolean || value instanceof Map || value instanceof Number || value instanceof String) { diff --git a/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java b/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java index 0fce3aea1..02b420b57 100644 --- a/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java +++ b/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java @@ -45,7 +45,7 @@ public Object resolve(Scope scope, List arguments) throws Exception { throw new GyroException("Unable to load the vault named '" + vaultName + "'. Ensure the vault is configured in .gyro/init.gyro."); } - return vault.get(key); + return new VaultSecret(key, vaultName, vault.get(key)); } } diff --git a/core/src/main/java/gyro/core/vault/VaultSecret.java b/core/src/main/java/gyro/core/vault/VaultSecret.java new file mode 100644 index 000000000..ce697bb60 --- /dev/null +++ b/core/src/main/java/gyro/core/vault/VaultSecret.java @@ -0,0 +1,76 @@ +package gyro.core.vault; + +import com.google.common.collect.ImmutableList; +import com.psddev.dari.util.StringUtils; +import gyro.core.resource.CustomValue; +import gyro.lang.ast.Node; +import gyro.lang.ast.value.ReferenceNode; +import gyro.lang.ast.value.ValueNode; + +import java.util.Arrays; +import java.util.Objects; + +public class VaultSecret implements CustomValue { + + private String key; + private String vault; + private String value; + + public VaultSecret(String key, String vault, String value) { + this.key = key; + this.vault = vault; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getVault() { + return vault; + } + + public String getValue() { + return value; + } + + public String getHash() { + return StringUtils.hex(StringUtils.sha512(getValue())); + } + + @Override + public String toString() { + + return getValue(); + } + + @Override + public Node toStateNode() { + ValueNode vaultKey = new ValueNode(getKey()); + ValueNode vaultNameNode = new ValueNode(getVault()); + ValueNode vaultHash = new ValueNode(getHash()); + + return new ReferenceNode(Arrays.asList(vaultKey, vaultNameNode, vaultHash), ImmutableList.of()) ; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof VaultSecret)) { + return false; + } + + VaultSecret that = (VaultSecret) o; + + return Objects.equals(getValue(), that.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } + +} From ea42d64d99b6c219a24eebc86528ded8ebd1b437 Mon Sep 17 00:00:00 2001 From: Hyoo Lim Date: Fri, 19 Jul 2019 13:26:33 -0400 Subject: [PATCH 11/25] Changes DiffableField.getValue to always prefer the value in scope. --- core/src/main/java/gyro/core/resource/DiffableField.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/gyro/core/resource/DiffableField.java b/core/src/main/java/gyro/core/resource/DiffableField.java index 1ce65d346..d4b566266 100644 --- a/core/src/main/java/gyro/core/resource/DiffableField.java +++ b/core/src/main/java/gyro/core/resource/DiffableField.java @@ -98,8 +98,13 @@ public boolean shouldBeDiffed() { public Object getValue(Diffable diffable) { DiffableScope scope = DiffableInternals.getScope(diffable); - if (scope != null && scope.get(getName()) instanceof CustomValue) { - return scope.get(getName()); + + if (scope != null) { + Object value = scope.get(name); + + if (value != null) { + return value; + } } return Reflections.invoke(getter, diffable); From 43ba29b22762dc9f3daf26c7f608ed9c3ca85818 Mon Sep 17 00:00:00 2001 From: Hyoo Lim Date: Fri, 19 Jul 2019 13:27:05 -0400 Subject: [PATCH 12/25] Moves CustomValue check to where it'd work. --- core/src/main/java/gyro/core/scope/State.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/gyro/core/scope/State.java b/core/src/main/java/gyro/core/scope/State.java index 91cc83696..40de0e148 100644 --- a/core/src/main/java/gyro/core/scope/State.java +++ b/core/src/main/java/gyro/core/scope/State.java @@ -221,9 +221,7 @@ private List toBodyNodes(Diffable diffable) { String key = field.getName(); - if (value instanceof CustomValue) { - body.add(toPairNode(key, ((CustomValue) value).toStateNode())); - } else if (value instanceof Boolean + if (value instanceof Boolean || value instanceof Map || value instanceof Number || value instanceof String) { @@ -287,6 +285,9 @@ private Node toNode(Object value) { return new ListNode(items); + } else if (value instanceof CustomValue) { + return ((CustomValue) value).toStateNode(); + } else if (value instanceof Map) { List entries = new ArrayList<>(); From e044a3b9f9e978b14154875b7f74d2e57cce6b8c Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Fri, 19 Jul 2019 13:34:30 -0400 Subject: [PATCH 13/25] Add 'vault-lookup' to state node for VaultSecret --- core/src/main/java/gyro/core/vault/VaultSecret.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/gyro/core/vault/VaultSecret.java b/core/src/main/java/gyro/core/vault/VaultSecret.java index ce697bb60..317bca713 100644 --- a/core/src/main/java/gyro/core/vault/VaultSecret.java +++ b/core/src/main/java/gyro/core/vault/VaultSecret.java @@ -46,11 +46,12 @@ public String toString() { @Override public Node toStateNode() { + ValueNode vaultResolver = new ValueNode("vault-lookup"); ValueNode vaultKey = new ValueNode(getKey()); ValueNode vaultNameNode = new ValueNode(getVault()); ValueNode vaultHash = new ValueNode(getHash()); - return new ReferenceNode(Arrays.asList(vaultKey, vaultNameNode, vaultHash), ImmutableList.of()) ; + return new ReferenceNode(Arrays.asList(vaultResolver, vaultKey, vaultNameNode, vaultHash), ImmutableList.of()) ; } @Override From dcd6c97ce1b3e6e21bb1e425434d8dac156416bb Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Fri, 19 Jul 2019 13:58:56 -0400 Subject: [PATCH 14/25] Add hash option to VaultReferenceResolver and VaulSecret --- .../java/gyro/core/vault/VaultReferenceResolver.java | 7 ++++++- core/src/main/java/gyro/core/vault/VaultSecret.java | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java b/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java index 02b420b57..fd63d2853 100644 --- a/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java +++ b/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java @@ -29,12 +29,17 @@ public Object resolve(Scope scope, List arguments) throws Exception { String key = null; String vaultName = "default"; + String hash = null; if (arguments.size() == 1) { key = (String) arguments.get(0); } else if (arguments.size() == 2) { key = (String) arguments.get(0); vaultName = (String) arguments.get(1); + } else if (arguments.size() == 3) { + key = (String) arguments.get(0); + vaultName = (String) arguments.get(1); + hash = (String) arguments.get(2); } RootScope rootScope = scope.getRootScope(); @@ -45,7 +50,7 @@ public Object resolve(Scope scope, List arguments) throws Exception { throw new GyroException("Unable to load the vault named '" + vaultName + "'. Ensure the vault is configured in .gyro/init.gyro."); } - return new VaultSecret(key, vaultName, vault.get(key)); + return new VaultSecret(key, vaultName, vault.get(key), hash); } } diff --git a/core/src/main/java/gyro/core/vault/VaultSecret.java b/core/src/main/java/gyro/core/vault/VaultSecret.java index 317bca713..97ff9599e 100644 --- a/core/src/main/java/gyro/core/vault/VaultSecret.java +++ b/core/src/main/java/gyro/core/vault/VaultSecret.java @@ -15,11 +15,13 @@ public class VaultSecret implements CustomValue { private String key; private String vault; private String value; + private String hash; - public VaultSecret(String key, String vault, String value) { + public VaultSecret(String key, String vault, String value, String hash) { this.key = key; this.vault = vault; this.value = value; + this.hash = hash; } public String getKey() { @@ -35,13 +37,16 @@ public String getValue() { } public String getHash() { + if (hash != null) { + return hash; + } + return StringUtils.hex(StringUtils.sha512(getValue())); } @Override public String toString() { - - return getValue(); + return String.format("hash/%s", getHash()); } @Override From 9ec60074d5960da4691bd533cff7624dbdb3cd9e Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Wed, 31 Jul 2019 08:23:31 -0400 Subject: [PATCH 15/25] All VaultSecret to hide its diff output --- core/src/main/java/gyro/core/diff/Change.java | 28 ++++++++++++------- .../java/gyro/core/resource/CustomValue.java | 2 +- .../java/gyro/core/vault/VaultSecret.java | 2 +- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/gyro/core/diff/Change.java b/core/src/main/java/gyro/core/diff/Change.java index a8cae6759..4177bcc07 100644 --- a/core/src/main/java/gyro/core/diff/Change.java +++ b/core/src/main/java/gyro/core/diff/Change.java @@ -10,13 +10,16 @@ import java.util.stream.Collectors; import com.google.common.collect.MapDifference; +import com.google.common.collect.MapDifference.ValueDifference; import com.google.common.collect.Maps; import gyro.core.GyroUI; +import gyro.core.resource.CustomValue; import gyro.core.resource.Diffable; import gyro.core.resource.DiffableField; import gyro.core.resource.DiffableInternals; import gyro.core.resource.DiffableType; import gyro.core.scope.State; +import gyro.core.vault.VaultSecret; import org.apache.commons.lang3.StringUtils; public abstract class Change { @@ -98,6 +101,17 @@ protected String stringify(Object value) { .stream() .map(e -> e.getKey() + ": " + stringify(e.getValue())) .collect(Collectors.joining(", ")) + " }"; + } else if (value instanceof ValueDifference) { + ValueDifference valueDifference = (ValueDifference) value; + if (valueDifference.rightValue() instanceof VaultSecret) { + return stringify(valueDifference.rightValue()); + } + + return String.format(" %s → %s", stringify(valueDifference.leftValue()), stringify(valueDifference.rightValue())); + + } else if (value instanceof Map.Entry) { + Map.Entry e = (Map.Entry) value; + return String.format(" %s: %s", e.getKey(), e.getValue()); } else if (value instanceof String) { return "'" + value + "'"; @@ -141,6 +155,9 @@ protected void writeDifference( } else if (p == null) { ui.write(" @|red -|@ %s", stringify(c)); + } else if (p instanceof VaultSecret) { + ui.write(" @|yellow ⟳|@ %s", stringify(p)); + } else { ui.write(" @|yellow ⟳|@ %s → %s", stringify(c), stringify(p)); } @@ -163,16 +180,7 @@ protected void writeDifference( MapDifference diff = Maps.difference((Map) currentValue, (Map) pendingValue); writeMapRemove(ui, diff.entriesOnlyOnLeft()); - - writeMap( - ui, - " @|yellow ⟳ {|@ %s @|yellow }|@", - diff.entriesDiffering(), - e -> String.format( - "%s → %s", - stringify(e.leftValue()), - stringify(e.rightValue()))); - + writeMap(ui, " @|yellow ⟳ {|@ %s @|yellow }|@", diff.entriesDiffering(), this::stringify); writeMapPut(ui, diff.entriesOnlyOnRight()); } diff --git a/core/src/main/java/gyro/core/resource/CustomValue.java b/core/src/main/java/gyro/core/resource/CustomValue.java index ac69c2da3..168b51623 100644 --- a/core/src/main/java/gyro/core/resource/CustomValue.java +++ b/core/src/main/java/gyro/core/resource/CustomValue.java @@ -4,6 +4,6 @@ public interface CustomValue { - public Node toStateNode(); + Node toStateNode(); } diff --git a/core/src/main/java/gyro/core/vault/VaultSecret.java b/core/src/main/java/gyro/core/vault/VaultSecret.java index 97ff9599e..e34113dca 100644 --- a/core/src/main/java/gyro/core/vault/VaultSecret.java +++ b/core/src/main/java/gyro/core/vault/VaultSecret.java @@ -46,7 +46,7 @@ public String getHash() { @Override public String toString() { - return String.format("hash/%s", getHash()); + return ""; } @Override From 73f9597193529dc6176e7d7cc6a438198e8aa89a Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Wed, 31 Jul 2019 08:27:39 -0400 Subject: [PATCH 16/25] Fix compilation error --- core/src/main/java/gyro/core/scope/RootScope.java | 3 --- .../main/java/gyro/core/vault/VaultDirectiveProcessor.java | 5 ++--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/gyro/core/scope/RootScope.java b/core/src/main/java/gyro/core/scope/RootScope.java index 77f797155..fc4227dbc 100644 --- a/core/src/main/java/gyro/core/scope/RootScope.java +++ b/core/src/main/java/gyro/core/scope/RootScope.java @@ -43,14 +43,11 @@ import gyro.core.resource.Resource; import gyro.core.resource.ResourcePlugin; import gyro.core.resource.TypeDescriptionDirectiveProcessor; -<<<<<<< HEAD import gyro.core.vault.VaultDirectiveProcessor; import gyro.core.vault.VaultPlugin; import gyro.core.vault.VaultReferenceResolver; -======= import gyro.core.validation.ValidationError; import gyro.core.validation.ValidationErrorException; ->>>>>>> master import gyro.core.virtual.VirtualDirectiveProcessor; import gyro.core.workflow.CreateDirectiveProcessor; import gyro.core.workflow.DeleteDirectiveProcessor; diff --git a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java index 8391db9f4..10f9e7ccd 100644 --- a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java +++ b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java @@ -21,9 +21,8 @@ public String getName() { @Override public void process(RootScope scope, DirectiveNode node) throws Exception { - List arguments = evaluateDirectiveArguments(scope, node, 1, 2); - String type = (String) arguments.get(0); - String name = arguments.size() == 1 ? "default" : (String) arguments.get(1); + String type = getArgument(scope, node, String.class, 0); + String name = getArgument(scope, node, String.class, 1); Scope bodyScope = evaluateBody(scope, node); VaultSettings settings = scope.getSettings(VaultSettings.class); From b51a4736d486f80dc6b10502cf95cb84f67ca3d3 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Wed, 14 Aug 2019 12:02:04 -0400 Subject: [PATCH 17/25] Serialize CustomValues to state --- core/src/main/java/gyro/core/scope/State.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/gyro/core/scope/State.java b/core/src/main/java/gyro/core/scope/State.java index 89a05baac..7a8d3dc6b 100644 --- a/core/src/main/java/gyro/core/scope/State.java +++ b/core/src/main/java/gyro/core/scope/State.java @@ -263,6 +263,8 @@ private List toBodyNodes(Diffable diffable) { } else { body.add(toPairNode(key, value)); } + } else if (value instanceof CustomValue) { + body.add(new PairNode(toNode(key), ((CustomValue) value).toStateNode())); } else { throw new GyroException(String.format( From 267bfe0e608c931e376b8886f13a8596d40ef39b Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Wed, 29 Jan 2020 14:05:13 -0500 Subject: [PATCH 18/25] Add back vault plugin, directive, and resolver --- core/src/main/java/gyro/core/scope/RootScope.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/gyro/core/scope/RootScope.java b/core/src/main/java/gyro/core/scope/RootScope.java index db053e0a6..fd08a77af 100644 --- a/core/src/main/java/gyro/core/scope/RootScope.java +++ b/core/src/main/java/gyro/core/scope/RootScope.java @@ -76,6 +76,9 @@ import gyro.core.scope.converter.ResourceToIdObject; import gyro.core.validation.ValidationError; import gyro.core.validation.ValidationErrorException; +import gyro.core.vault.VaultDirectiveProcessor; +import gyro.core.vault.VaultPlugin; +import gyro.core.vault.VaultReferenceResolver; import gyro.core.virtual.VirtualDirectiveProcessor; import gyro.core.workflow.CreateDirectiveProcessor; import gyro.core.workflow.DefineDirectiveProcessor; @@ -124,7 +127,8 @@ public RootScope(String file, FileBackend backend, RootScope current, Set getSettings(PluginSettings.class).getPlugins().add(p)); Stream.of( @@ -148,6 +152,7 @@ public RootScope(String file, FileBackend backend, RootScope current, Set getSettings(DirectiveSettings.class).addProcessor(p)); Stream.of( - FinderReferenceResolver.class) + FinderReferenceResolver.class, + VaultReferenceResolver.class) .forEach(r -> getSettings(ReferenceSettings.class).addResolver(r)); Stream.of( @@ -265,7 +271,9 @@ public T findResourceById(Class resourceClass, Object id DiffableField idField = type.getIdField(); if (idField == null) { - throw new GyroException(String.format("Unable to find @Id on a getter in %s", resourceClass.getSimpleName())); + throw new GyroException(String.format( + "Unable to find @Id on a getter in %s", + resourceClass.getSimpleName())); } return findResourcesByClass(resourceClass) From 54d3d09083abff89ac941e937ad358cabbb9b278 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Wed, 29 Jan 2020 14:07:46 -0500 Subject: [PATCH 19/25] Convert vault directive and resolver to use @Type() --- .../core/vault/VaultDirectiveProcessor.java | 19 +++++++------------ .../core/vault/VaultReferenceResolver.java | 14 ++++++-------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java index 10f9e7ccd..6ef5e2913 100644 --- a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java +++ b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java @@ -1,24 +1,20 @@ package gyro.core.vault; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; + import com.google.common.base.CaseFormat; import gyro.core.GyroException; import gyro.core.Reflections; +import gyro.core.Type; import gyro.core.directive.DirectiveProcessor; import gyro.core.scope.RootScope; import gyro.core.scope.Scope; import gyro.lang.ast.block.DirectiveNode; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Method; -import java.util.List; - +@Type("vault") public class VaultDirectiveProcessor extends DirectiveProcessor { - @Override - public String getName() { - return "vault"; - } - @Override public void process(RootScope scope, DirectiveNode node) throws Exception { String type = getArgument(scope, node, String.class, 0); @@ -40,8 +36,8 @@ public void process(RootScope scope, DirectiveNode node) throws Exception { if (setter != null && !"setName".equals(setter.getName())) { Object value = scope.convertValue( - setter.getGenericParameterTypes()[0], - bodyScope.get(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, property.getName()))); + setter.getGenericParameterTypes()[0], + bodyScope.get(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, property.getName()))); if (value != null) { Reflections.invoke(setter, vault, value); @@ -56,5 +52,4 @@ public void process(RootScope scope, DirectiveNode node) throws Exception { settings.getVaultsByName().put(name, vault); } - } diff --git a/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java b/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java index fd63d2853..55c6cd440 100644 --- a/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java +++ b/core/src/main/java/gyro/core/vault/VaultReferenceResolver.java @@ -1,25 +1,22 @@ package gyro.core.vault; +import java.util.List; + import gyro.core.GyroException; +import gyro.core.Type; import gyro.core.reference.ReferenceResolver; import gyro.core.scope.RootScope; import gyro.core.scope.Scope; -import java.util.List; - /** * Examples: * * $(vault-lookup secret-key) - Grab key from the 'default' vault * $(vault-lookup secret-key vault) - Grab key from the vault provided by vault */ +@Type("vault-lookup") public class VaultReferenceResolver extends ReferenceResolver { - @Override - public String getName() { - return "vault-lookup"; - } - @Override public Object resolve(Scope scope, List arguments) throws Exception { @@ -47,7 +44,8 @@ public Object resolve(Scope scope, List arguments) throws Exception { Vault vault = settings.getVaultsByName().get(vaultName); if (vault == null) { - throw new GyroException("Unable to load the vault named '" + vaultName + "'. Ensure the vault is configured in .gyro/init.gyro."); + throw new GyroException("Unable to load the vault named '" + vaultName + + "'. Ensure the vault is configured in .gyro/init.gyro."); } return new VaultSecret(key, vaultName, vault.get(key), hash); From 3be1cd267a6ce54fac20af224b06db39cf0a1822 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Wed, 29 Jan 2020 14:37:47 -0500 Subject: [PATCH 20/25] Fix compile errors --- core/src/main/java/gyro/core/scope/State.java | 2 +- .../java/gyro/core/vault/VaultCommand.java | 41 +++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/gyro/core/scope/State.java b/core/src/main/java/gyro/core/scope/State.java index 717bcb6c0..f579d117b 100644 --- a/core/src/main/java/gyro/core/scope/State.java +++ b/core/src/main/java/gyro/core/scope/State.java @@ -286,7 +286,7 @@ private List toBodyNodes(Diffable diffable, Resource resource) { body.add(toPairNode(key, value, resource)); } } else if (value instanceof CustomValue) { - body.add(new PairNode(toNode(key), ((CustomValue) value).toStateNode())); + body.add(new PairNode(toNode(key, resource), ((CustomValue) value).toStateNode())); } else { throw new GyroException(String.format( diff --git a/core/src/main/java/gyro/core/vault/VaultCommand.java b/core/src/main/java/gyro/core/vault/VaultCommand.java index 08a520038..72d19e8ef 100644 --- a/core/src/main/java/gyro/core/vault/VaultCommand.java +++ b/core/src/main/java/gyro/core/vault/VaultCommand.java @@ -1,17 +1,19 @@ package gyro.core.vault; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import gyro.core.GyroCore; import gyro.core.GyroException; +import gyro.core.LocalFileBackend; import gyro.core.command.AbstractCommand; import gyro.core.scope.RootScope; import io.airlift.airline.Arguments; import io.airlift.airline.Command; import io.airlift.airline.Option; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - @Command(name = "vault", description = "Store/retrieve secrets") public class VaultCommand extends AbstractCommand { @@ -39,16 +41,31 @@ public void setVaultName(String vaultName) { @Override protected void doExecute() throws Exception { + Path rootDir = GyroCore.getRootDirectory(); + + if (rootDir == null) { + throw new GyroException( + "Not a gyro project directory, use 'gyro init ...' to create one. See 'gyro help init' for detailed usage."); + } + if (getArguments().isEmpty()) { throw new GyroException("Expected 'gyro vault get|put key' or 'gyro vault list'"); } - RootScope scope = (RootScope) getInit(); + RootScope scope = new RootScope( + "../../" + GyroCore.INIT_FILE, + new LocalFileBackend(rootDir.resolve(".gyro/state")), + null, + null); + + scope.evaluate(); + VaultSettings settings = scope.getSettings(VaultSettings.class); Vault vault = settings.getVaultsByName().get(getVaultName()); if (vault == null) { - throw new GyroException("Unable to load the vault named '" + getVaultName() + "'. Ensure the vault is configured in .gyro/init.gyro."); + throw new GyroException("Unable to load the vault named '" + getVaultName() + + "'. Ensure the vault is configured in .gyro/init.gyro."); } String command = getArguments().get(0); @@ -67,15 +84,15 @@ protected void doExecute() throws Exception { GyroCore.ui().write(vault.get(key) + "\n"); } } else if ("remove".equalsIgnoreCase(command) || "rm".equalsIgnoreCase(command)) { - String key = getArguments().size() >= 2 ? getArguments().get(1) : null; + String key = getArguments().size() >= 2 ? getArguments().get(1) : null; - if (key == null) { - throw new GyroException("Key argument missing. Expected 'gyro vault get '"); - } + if (key == null) { + throw new GyroException("Key argument missing. Expected 'gyro vault get '"); + } - vault.remove(key); + vault.remove(key); - GyroCore.ui().write("\nKey '%s' was removed in the '%s' vault.\n", key, getVaultName()); + GyroCore.ui().write("\nKey '%s' was removed in the '%s' vault.\n", key, getVaultName()); } else if ("put".equalsIgnoreCase(command)) { String key = getArguments().size() >= 2 ? getArguments().get(1) : null; String value = getArguments().size() >= 3 ? getArguments().get(2) : null; From 679d8f4417ad83c2b190fee99017aa17274ccb87 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Wed, 29 Jan 2020 15:01:34 -0500 Subject: [PATCH 21/25] Checkstyle fixes --- core/src/main/java/gyro/core/diff/Change.java | 6 +- .../main/java/gyro/core/vault/LocalVault.java | 81 ++++++++++--------- .../core/vault/LocalVaultSecretResource.java | 4 +- .../java/gyro/core/vault/VaultCommand.java | 2 +- .../java/gyro/core/vault/VaultPlugin.java | 4 +- .../java/gyro/core/vault/VaultSecret.java | 8 +- .../java/gyro/core/vault/VaultSettings.java | 4 +- 7 files changed, 59 insertions(+), 50 deletions(-) diff --git a/core/src/main/java/gyro/core/diff/Change.java b/core/src/main/java/gyro/core/diff/Change.java index fe7f3c65f..ec6917c2d 100644 --- a/core/src/main/java/gyro/core/diff/Change.java +++ b/core/src/main/java/gyro/core/diff/Change.java @@ -29,7 +29,6 @@ import com.google.common.collect.MapDifference.ValueDifference; import com.google.common.collect.Maps; import gyro.core.GyroUI; -import gyro.core.resource.CustomValue; import gyro.core.resource.Diffable; import gyro.core.resource.DiffableField; import gyro.core.resource.DiffableInternals; @@ -123,7 +122,10 @@ protected String stringify(Object value) { return stringify(valueDifference.rightValue()); } - return String.format(" %s → %s", stringify(valueDifference.leftValue()), stringify(valueDifference.rightValue())); + return String.format( + " %s → %s", + stringify(valueDifference.leftValue()), + stringify(valueDifference.rightValue())); } else if (value instanceof Map.Entry) { Map.Entry e = (Map.Entry) value; diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index 3a412633e..ab1ad9f64 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -1,5 +1,34 @@ package gyro.core.vault; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermissions; +import java.security.SecureRandom; +import java.security.spec.KeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + import com.google.common.collect.ImmutableSet; import com.psddev.dari.util.IoUtils; import gyro.core.GyroCore; @@ -25,37 +54,7 @@ import gyro.util.Bug; import org.apache.commons.codec.binary.Base64; -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.PosixFilePermissions; -import java.security.SecureRandom; -import java.security.spec.KeySpec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; -import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import static java.nio.file.attribute.PosixFilePermission.*; @Type("local") public class LocalVault extends Vault { @@ -103,7 +102,10 @@ public Integer getKeyIterations() { public void setKeyIterations(Integer keyIterations) { if (keyIterations != null && keyIterations < 10000) { - throw new GyroException(String.format(Locale.getDefault(), "Key iterations must be greater than %,d per NIST recommended guidelines.", 10000)); + throw new GyroException(String.format( + Locale.getDefault(), + "Key iterations must be greater than %,d per NIST recommended guidelines.", + 10000)); } this.keyIterations = keyIterations; @@ -118,7 +120,8 @@ public char[] key() { byte[] key = Base64.decodeBase64(IoUtils.toByteArray(new File(getKeyPath()))); return new String(key, StandardCharsets.UTF_8).toCharArray(); } catch (FileNotFoundException ex) { - throw new GyroException("Encryption key for '" + getName() + "' vault not found at path '" + getKeyPath() + "'"); + throw new GyroException( + "Encryption key for '" + getName() + "' vault not found at path '" + getKeyPath() + "'"); } catch (IOException ex) { throw new GyroException("Unable to load encryption key for '" + getName() + "' vault.", ex); } @@ -128,7 +131,8 @@ public char[] key() { public String get(String key) { RootScope vaultScope = loadVault(); DiffableType type = DiffableType.getInstance(LocalVaultSecretResource.class); - LocalVaultSecretResource secret = (LocalVaultSecretResource) vaultScope.findResource(type.getName() + "::" + key); + LocalVaultSecretResource secret = (LocalVaultSecretResource) vaultScope.findResource( + type.getName() + "::" + key); if (secret != null) { return decrypt(secret); @@ -179,7 +183,9 @@ private RootScope loadVault() { if (!Files.exists(vaultPath())) { try { Files.createDirectories(vaultPath().getParent()); - Files.createFile(vaultPath(), PosixFilePermissions.asFileAttribute(ImmutableSet.of(OWNER_READ, OWNER_WRITE))); + Files.createFile( + vaultPath(), + PosixFilePermissions.asFileAttribute(ImmutableSet.of(OWNER_READ, OWNER_WRITE))); } catch (Exception ex) { throw new GyroException(ex); } @@ -191,7 +197,8 @@ private RootScope loadVault() { null, ImmutableSet.of()); - vault.getRootScope().put(DiffableType.getInstance(LocalVaultSecretResource.class).getName(), LocalVaultSecretResource.class); + vault.getRootScope() + .put(DiffableType.getInstance(LocalVaultSecretResource.class).getName(), LocalVaultSecretResource.class); try { vault.evaluate(); @@ -376,7 +383,7 @@ private LocalVaultSecretResource encrypt(String plaintext) { SecretKey tmp = factory.generateSecret(spec); SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES"); - byte[] iv = new byte[128/8]; + byte[] iv = new byte[128 / 8]; SecureRandom.getInstanceStrong().nextBytes(iv); IvParameterSpec ivspec = new IvParameterSpec(iv); diff --git a/core/src/main/java/gyro/core/vault/LocalVaultSecretResource.java b/core/src/main/java/gyro/core/vault/LocalVaultSecretResource.java index 7977690c5..8967c0a55 100644 --- a/core/src/main/java/gyro/core/vault/LocalVaultSecretResource.java +++ b/core/src/main/java/gyro/core/vault/LocalVaultSecretResource.java @@ -1,5 +1,7 @@ package gyro.core.vault; +import java.util.Set; + import gyro.core.GyroUI; import gyro.core.Namespace; import gyro.core.Type; @@ -7,8 +9,6 @@ import gyro.core.scope.State; import org.apache.commons.codec.binary.Base64; -import java.util.Set; - @Type("vault-secret") @Namespace("gyro") public class LocalVaultSecretResource extends Resource { diff --git a/core/src/main/java/gyro/core/vault/VaultCommand.java b/core/src/main/java/gyro/core/vault/VaultCommand.java index 72d19e8ef..79a2cfd11 100644 --- a/core/src/main/java/gyro/core/vault/VaultCommand.java +++ b/core/src/main/java/gyro/core/vault/VaultCommand.java @@ -20,7 +20,7 @@ public class VaultCommand extends AbstractCommand { @Arguments private List arguments = new ArrayList<>(); - @Option(name = { "--vault" }, description = "The vault name to manipulate or query.") + @Option(name = "--vault", description = "The vault name to manipulate or query.") private String vaultName = "default"; public List getArguments() { diff --git a/core/src/main/java/gyro/core/vault/VaultPlugin.java b/core/src/main/java/gyro/core/vault/VaultPlugin.java index 0d431ab60..d9ef0eaf2 100644 --- a/core/src/main/java/gyro/core/vault/VaultPlugin.java +++ b/core/src/main/java/gyro/core/vault/VaultPlugin.java @@ -1,12 +1,12 @@ package gyro.core.vault; +import java.util.Optional; + import gyro.core.GyroException; import gyro.core.Type; import gyro.core.plugin.Plugin; import gyro.core.scope.RootScope; -import java.util.Optional; - public class VaultPlugin extends Plugin { @Override diff --git a/core/src/main/java/gyro/core/vault/VaultSecret.java b/core/src/main/java/gyro/core/vault/VaultSecret.java index e34113dca..17b330ee1 100644 --- a/core/src/main/java/gyro/core/vault/VaultSecret.java +++ b/core/src/main/java/gyro/core/vault/VaultSecret.java @@ -1,5 +1,8 @@ package gyro.core.vault; +import java.util.Arrays; +import java.util.Objects; + import com.google.common.collect.ImmutableList; import com.psddev.dari.util.StringUtils; import gyro.core.resource.CustomValue; @@ -7,9 +10,6 @@ import gyro.lang.ast.value.ReferenceNode; import gyro.lang.ast.value.ValueNode; -import java.util.Arrays; -import java.util.Objects; - public class VaultSecret implements CustomValue { private String key; @@ -56,7 +56,7 @@ public Node toStateNode() { ValueNode vaultNameNode = new ValueNode(getVault()); ValueNode vaultHash = new ValueNode(getHash()); - return new ReferenceNode(Arrays.asList(vaultResolver, vaultKey, vaultNameNode, vaultHash), ImmutableList.of()) ; + return new ReferenceNode(Arrays.asList(vaultResolver, vaultKey, vaultNameNode, vaultHash), ImmutableList.of()); } @Override diff --git a/core/src/main/java/gyro/core/vault/VaultSettings.java b/core/src/main/java/gyro/core/vault/VaultSettings.java index 23ce873f1..a34c9a922 100644 --- a/core/src/main/java/gyro/core/vault/VaultSettings.java +++ b/core/src/main/java/gyro/core/vault/VaultSettings.java @@ -1,10 +1,10 @@ package gyro.core.vault; -import gyro.core.scope.Settings; - import java.util.HashMap; import java.util.Map; +import gyro.core.scope.Settings; + public class VaultSettings extends Settings { private Map> vaultClasses; From b71391c5de0d76984c7ae578868b3ce9ea3a57b0 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Mon, 3 Feb 2020 10:23:34 -0500 Subject: [PATCH 22/25] Use 'default' as the name if a name is not provided --- .../main/java/gyro/core/vault/VaultDirectiveProcessor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java index 6ef5e2913..de9a6eaa3 100644 --- a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java +++ b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java @@ -19,8 +19,11 @@ public class VaultDirectiveProcessor extends DirectiveProcessor { public void process(RootScope scope, DirectiveNode node) throws Exception { String type = getArgument(scope, node, String.class, 0); String name = getArgument(scope, node, String.class, 1); - Scope bodyScope = evaluateBody(scope, node); + if (name == null) { + name = "default"; + } + Scope bodyScope = evaluateBody(scope, node); VaultSettings settings = scope.getSettings(VaultSettings.class); Class vaultClass = settings.getVaultClasses().get(type); From 1bced32cd44a25acf1bbbdf1e6451d7ae14df857 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Mon, 3 Feb 2020 13:26:43 -0500 Subject: [PATCH 23/25] Only evaluate files in the local vault This fixes an issue where all files were being evaluated when loading the local vault. This would fail because the vault itself is not defined in this use case. --- core/src/main/java/gyro/core/vault/LocalVault.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/gyro/core/vault/LocalVault.java b/core/src/main/java/gyro/core/vault/LocalVault.java index ab1ad9f64..84bd35419 100644 --- a/core/src/main/java/gyro/core/vault/LocalVault.java +++ b/core/src/main/java/gyro/core/vault/LocalVault.java @@ -193,7 +193,7 @@ private RootScope loadVault() { RootScope vault = new RootScope( vaultPath().toString(), - new LocalFileBackend(GyroCore.getRootDirectory()), + new LocalFileBackend(vaultPath()), null, ImmutableSet.of()); From f42d62adcaa7c4e9387dbb80cd76d7196e4f5203 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Mon, 3 Feb 2020 13:27:33 -0500 Subject: [PATCH 24/25] Allow duplicate vaults This is just a temporary fix. Need to investigate why this fails when only a single instance of a vault is defined. --- .../main/java/gyro/core/vault/VaultDirectiveProcessor.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java index de9a6eaa3..0aef36ece 100644 --- a/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java +++ b/core/src/main/java/gyro/core/vault/VaultDirectiveProcessor.java @@ -48,10 +48,6 @@ public void process(RootScope scope, DirectiveNode node) throws Exception { } } - if (settings.getVaultsByName().containsKey(name)) { - throw new GyroException("A vault with the name '" + name + "' was previously defined."); - } - settings.getVaultsByName().put(name, vault); } From 30d3cc52174b5eb9211feb75ff4f610b6b20e8bf Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Mon, 3 Feb 2020 13:28:02 -0500 Subject: [PATCH 25/25] Compare strings to VaultSecret.getValue() --- core/src/main/java/gyro/core/vault/VaultSecret.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/gyro/core/vault/VaultSecret.java b/core/src/main/java/gyro/core/vault/VaultSecret.java index 17b330ee1..701445601 100644 --- a/core/src/main/java/gyro/core/vault/VaultSecret.java +++ b/core/src/main/java/gyro/core/vault/VaultSecret.java @@ -65,6 +65,10 @@ public boolean equals(Object o) { return true; } + if (o instanceof String) { + return Objects.equals(getValue(), o); + } + if (!(o instanceof VaultSecret)) { return false; }