From 19920eeed1a211cbbcab08063493a7f120fb7e2a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 01/36] GUACAMOLE-641: Allow token retrieval/generation to fail with an error. --- .../net/auth/TokenInjectingUserContext.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java index a1ede96a5..1f6d3d60e 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java @@ -83,8 +83,13 @@ public class TokenInjectingUserContext extends DelegatingUserContext { * @return * The tokens which should be added to the in-progress call to * connect(). + * + * @throws GuacamoleException + * If the tokens applicable to the given connection cannot be + * generated. */ - protected Map getTokens(Connection connection) { + protected Map getTokens(Connection connection) + throws GuacamoleException { return tokens; } @@ -100,8 +105,13 @@ public class TokenInjectingUserContext extends DelegatingUserContext { * @return * The tokens which should be added to the in-progress call to * connect(). + * + * @throws GuacamoleException + * If the tokens applicable to the given connection group cannot be + * generated. */ - protected Map getTokens(ConnectionGroup connectionGroup) { + protected Map getTokens(ConnectionGroup connectionGroup) + throws GuacamoleException { return tokens; } From 0ac67b8cf81bfa654456d27f3c50ff89026d104a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 02/36] GUACAMOLE-641: Provide strict filtering mode for TokenFilter which disallows undefined tokens. --- .../GuacamoleTokenUndefinedException.java | 96 ++++++++++++++ .../apache/guacamole/token/TokenFilter.java | 118 ++++++++++++++++-- 2 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 guacamole-ext/src/main/java/org/apache/guacamole/token/GuacamoleTokenUndefinedException.java diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/token/GuacamoleTokenUndefinedException.java b/guacamole-ext/src/main/java/org/apache/guacamole/token/GuacamoleTokenUndefinedException.java new file mode 100644 index 000000000..1a80e4788 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/token/GuacamoleTokenUndefinedException.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.token; + +import org.apache.guacamole.GuacamoleServerException; + +/** + * An exception thrown when a token cannot be substituted because it has no + * corresponding value. Additional information describing the undefined token + * is provided. + */ +public class GuacamoleTokenUndefinedException extends GuacamoleServerException { + + /** + * The name of the token that is undefined. + */ + private final String tokenName; + + /** + * Creates a new GuacamoleTokenUndefinedException with the given message, + * cause, and associated undefined token name. + * + * @param message + * A human readable description of the exception that occurred. + * + * @param cause + * The cause of this exception. + * + * @param tokenName + * The name of the token which has no defined value. + */ + public GuacamoleTokenUndefinedException(String message, Throwable cause, + String tokenName) { + super(message, cause); + this.tokenName = tokenName; + } + + /** + * Creates a new GuacamoleTokenUndefinedException with the given + * message and associated undefined token name. + * + * @param message + * A human readable description of the exception that occurred. + * + * @param tokenName + * The name of the token which has no defined value. + */ + public GuacamoleTokenUndefinedException(String message, String tokenName) { + super(message); + this.tokenName = tokenName; + } + + /** + * Creates a new GuacamoleTokenUndefinedException with the given cause + * and associated undefined token name. + * + * @param cause + * The cause of this exception. + * + * @param tokenName + * The name of the token which has no defined value. + */ + public GuacamoleTokenUndefinedException(Throwable cause, String tokenName) { + super(cause); + this.tokenName = tokenName; + } + + /** + * Returns the name of the token which has no defined value, causing this + * exception to be thrown. + * + * @return + * The name of the token which has no defined value. + */ + public String getTokenName() { + return tokenName; + } + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/token/TokenFilter.java b/guacamole-ext/src/main/java/org/apache/guacamole/token/TokenFilter.java index d1570e441..a9766face 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/token/TokenFilter.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/token/TokenFilter.java @@ -162,19 +162,31 @@ public class TokenFilter { tokenValues.clear(); tokenValues.putAll(tokens); } - + /** * Filters the given string, replacing any tokens with their corresponding - * values. + * values. Handling of undefined tokens depends on the value given for the + * strict flag. * * @param input * The string to filter. * + * @param strict + * Whether to disallow tokens which lack values from existing in the + * string. If true, an exception will be thrown if any tokens in the + * string lack corresponding values. If false, tokens which lack values + * will be interpreted as literals. + * * @return * A copy of the input string, with any tokens replaced with their * corresponding values. + * + * @throws GuacamoleTokenUndefinedException + * If the strict flag is set to true and at least one token in the + * given string has no corresponding value. */ - public String filter(String input) { + private String filter(String input, boolean strict) + throws GuacamoleTokenUndefinedException { StringBuilder output = new StringBuilder(); Matcher tokenMatcher = tokenPattern.matcher(input); @@ -209,10 +221,20 @@ public class TokenFilter { String tokenName = tokenMatcher.group(TOKEN_NAME_GROUP); String tokenValue = getToken(tokenName); - // If token is unknown, interpret as literal + // If token is unknown, interpretation depends on whether + // strict mode is enabled if (tokenValue == null) { + + // Fail outright if strict mode is enabled + if (strict) + throw new GuacamoleTokenUndefinedException("Token " + + "has no defined value.", tokenName); + + // If strict mode is NOT enabled, simply interpret as + // a literal String notToken = tokenMatcher.group(TOKEN_GROUP); output.append(notToken); + } // Otherwise, check for modifiers and substitute value appropriately @@ -247,19 +269,71 @@ public class TokenFilter { // Update last regex match endOfLastMatch = tokenMatcher.end(); - + } // Append any remaining non-token text output.append(input.substring(endOfLastMatch)); - + return output.toString(); - + + } + + /** + * Filters the given string, replacing any tokens with their corresponding + * values. Any tokens present in the given string which lack values will + * be interpreted as literals. + * + * @param input + * The string to filter. + * + * @return + * A copy of the input string, with any tokens replaced with their + * corresponding values. + */ + public String filter(String input) { + + // Filter with strict mode disabled (should always succeed) + try { + return filter(input, false); + } + + // GuacamoleTokenUndefinedException cannot be thrown when strict mode + // is disabled + catch (GuacamoleTokenUndefinedException e) { + throw new IllegalStateException("filter() threw " + + "GuacamoleTokenUndefinedException despite strict mode " + + "being disabled."); + } + + } + + /** + * Filters the given string, replacing any tokens with their corresponding + * values. If any token in the given string has no defined value within + * this TokenFilter, a GuacamoleTokenUndefinedException will be thrown. + * + * @param input + * The string to filter. + * + * @return + * A copy of the input string, with any tokens replaced with their + * corresponding values. + * + * @throws GuacamoleTokenUndefinedException + * If at least one token in the given string has no corresponding + * value. + */ + public String filterStrict(String input) + throws GuacamoleTokenUndefinedException { + return filter(input, true); } /** * Given an arbitrary map containing String values, replace each non-null - * value with the corresponding filtered value. + * value with the corresponding filtered value. Any tokens present in the + * values of the given map which lack defined values within this + * TokenFilter will be interpreted as literals. * * @param map * The map whose values should be filtered. @@ -273,6 +347,34 @@ public class TokenFilter { String value = entry.getValue(); if (value != null) entry.setValue(filter(value)); + + } + + } + + /** + * Given an arbitrary map containing String values, replace each non-null + * value with the corresponding filtered value. If any token in any string + * has no defined value within this TokenFilter, a + * GuacamoleTokenUndefinedException will be thrown. + * + * @param map + * The map whose values should be filtered. + * + * @throws GuacamoleTokenUndefinedException + * If at least one token in at least one string has no corresponding + * value. + */ + public void filterValuesStrict(Map map) + throws GuacamoleTokenUndefinedException { + + // For each map entry + for (Map.Entry entry : map.entrySet()) { + + // If value is non-null, filter value through this TokenFilter + String value = entry.getValue(); + if (value != null) + entry.setValue(filterStrict(value)); } From 6145a79f5d68158233551dc4ca10a1be29c0ff21 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 03/36] GUACAMOLE-641: Add generic vault support with an initial Azure Key Vault implementation. --- doc/licenses/adal4j-1.6.7/LICENSE | 21 ++ doc/licenses/adal4j-1.6.7/README | 8 + doc/licenses/adal4j-1.6.7/dep-coordinates.txt | 1 + doc/licenses/apache-commons-lang-3.8.1/NOTICE | 5 + doc/licenses/apache-commons-lang-3.8.1/README | 8 + .../dep-coordinates.txt | 1 + doc/licenses/asm-8.0.1/LICENSE.txt | 28 ++ doc/licenses/asm-8.0.1/README | 8 + doc/licenses/asm-8.0.1/dep-coordinates.txt | 1 + .../autorest-client-runtime-1.7.4/LICENSE | 21 ++ .../autorest-client-runtime-1.7.4/README | 9 + .../dep-coordinates.txt | 2 + .../azure-annotations-1.10.0/License.txt | 28 ++ doc/licenses/azure-annotations-1.10.0/README | 9 + .../dep-coordinates.txt | 1 + .../azure-sdk-for-java-1.2.4/LICENSE.txt | 21 ++ doc/licenses/azure-sdk-for-java-1.2.4/README | 8 + .../dep-coordinates.txt | 5 + doc/licenses/gson-2.8.0/README | 8 + doc/licenses/gson-2.8.0/dep-coordinates.txt | 1 + .../jackson-2.13.1/dep-coordinates.txt | 1 + doc/licenses/joda-time-2.10.8/NOTICE | 2 + doc/licenses/joda-time-2.10.8/README | 8 + .../joda-time-2.10.8/dep-coordinates.txt | 1 + doc/licenses/json-smart-2.4.2/LICENSE | 202 +++++++++++++ doc/licenses/json-smart-2.4.2/README | 8 + .../json-smart-2.4.2/dep-coordinates.txt | 2 + doc/licenses/lang-tag-1.5/README | 8 + doc/licenses/lang-tag-1.5/dep-coordinates.txt | 1 + doc/licenses/nimbus-content-type-2.1/README | 8 + .../dep-coordinates.txt | 1 + doc/licenses/nimbus-jose-jwt-9.8.1/README | 8 + .../nimbus-jose-jwt-9.8.1/dep-coordinates.txt | 1 + doc/licenses/oauth2-oidc-sdk-9.4/README | 9 + .../oauth2-oidc-sdk-9.4/dep-coordinates.txt | 1 + doc/licenses/okhttp-3.14.7/README | 8 + .../okhttp-3.14.7/dep-coordinates.txt | 3 + doc/licenses/okio-1.17.2/README | 8 + doc/licenses/okio-1.17.2/dep-coordinates.txt | 1 + doc/licenses/retrofit-2.7.2/README | 8 + .../retrofit-2.7.2/dep-coordinates.txt | 3 + doc/licenses/rxjava-1.3.8/LICENSE | 202 +++++++++++++ doc/licenses/rxjava-1.3.8/README | 8 + doc/licenses/rxjava-1.3.8/dep-coordinates.txt | 1 + doc/licenses/snakeyaml-1.27/README | 8 + .../snakeyaml-1.27/dep-coordinates.txt | 1 + .../stephenc-jcip-annotations-1.0-1/README | 8 + .../dep-coordinates.txt | 1 + extensions/guacamole-auth-vault/.ratignore | 0 .../guacamole-auth-vault-azure/.ratignore | 0 .../guacamole-auth-vault-azure/pom.xml | 194 ++++++++++++ .../AzureKeyVaultAuthenticationProvider.java | 47 +++ ...eKeyVaultAuthenticationProviderModule.java | 61 ++++ .../AzureKeyVaultAuthenticationException.java | 57 ++++ .../AzureKeyVaultConfigurationService.java | 135 +++++++++ .../azure/conf/AzureKeyVaultCredentials.java | 115 +++++++ .../secret/AzureKeyVaultSecretService.java | 99 ++++++ .../src/main/resources/guac-manifest.json | 16 + .../guacamole-auth-vault-base/.ratignore | 0 .../modules/guacamole-auth-vault-base/pom.xml | 70 +++++ .../vault/VaultAuthenticationProvider.java | 63 ++++ .../VaultAuthenticationProviderModule.java | 98 ++++++ .../vault/conf/VaultConfigurationService.java | 107 +++++++ .../auth/vault/secret/VaultSecretService.java | 67 +++++ .../auth/vault/user/VaultUserContext.java | 281 ++++++++++++++++++ .../vault/user/VaultUserContextFactory.java | 46 +++ .../src/main/resources/translations/en.json | 7 + .../guacamole-auth-vault-dist/.ratignore | 0 .../modules/guacamole-auth-vault-dist/pom.xml | 63 ++++ .../src/main/assembly/dist.xml | 54 ++++ extensions/guacamole-auth-vault/pom.xml | 67 +++++ extensions/pom.xml | 1 + pom.xml | 7 +- 73 files changed, 2369 insertions(+), 1 deletion(-) create mode 100644 doc/licenses/adal4j-1.6.7/LICENSE create mode 100644 doc/licenses/adal4j-1.6.7/README create mode 100644 doc/licenses/adal4j-1.6.7/dep-coordinates.txt create mode 100644 doc/licenses/apache-commons-lang-3.8.1/NOTICE create mode 100644 doc/licenses/apache-commons-lang-3.8.1/README create mode 100644 doc/licenses/apache-commons-lang-3.8.1/dep-coordinates.txt create mode 100644 doc/licenses/asm-8.0.1/LICENSE.txt create mode 100644 doc/licenses/asm-8.0.1/README create mode 100644 doc/licenses/asm-8.0.1/dep-coordinates.txt create mode 100644 doc/licenses/autorest-client-runtime-1.7.4/LICENSE create mode 100644 doc/licenses/autorest-client-runtime-1.7.4/README create mode 100644 doc/licenses/autorest-client-runtime-1.7.4/dep-coordinates.txt create mode 100644 doc/licenses/azure-annotations-1.10.0/License.txt create mode 100644 doc/licenses/azure-annotations-1.10.0/README create mode 100644 doc/licenses/azure-annotations-1.10.0/dep-coordinates.txt create mode 100644 doc/licenses/azure-sdk-for-java-1.2.4/LICENSE.txt create mode 100644 doc/licenses/azure-sdk-for-java-1.2.4/README create mode 100644 doc/licenses/azure-sdk-for-java-1.2.4/dep-coordinates.txt create mode 100644 doc/licenses/gson-2.8.0/README create mode 100644 doc/licenses/gson-2.8.0/dep-coordinates.txt create mode 100644 doc/licenses/joda-time-2.10.8/NOTICE create mode 100644 doc/licenses/joda-time-2.10.8/README create mode 100644 doc/licenses/joda-time-2.10.8/dep-coordinates.txt create mode 100644 doc/licenses/json-smart-2.4.2/LICENSE create mode 100644 doc/licenses/json-smart-2.4.2/README create mode 100644 doc/licenses/json-smart-2.4.2/dep-coordinates.txt create mode 100644 doc/licenses/lang-tag-1.5/README create mode 100644 doc/licenses/lang-tag-1.5/dep-coordinates.txt create mode 100644 doc/licenses/nimbus-content-type-2.1/README create mode 100644 doc/licenses/nimbus-content-type-2.1/dep-coordinates.txt create mode 100644 doc/licenses/nimbus-jose-jwt-9.8.1/README create mode 100644 doc/licenses/nimbus-jose-jwt-9.8.1/dep-coordinates.txt create mode 100644 doc/licenses/oauth2-oidc-sdk-9.4/README create mode 100644 doc/licenses/oauth2-oidc-sdk-9.4/dep-coordinates.txt create mode 100644 doc/licenses/okhttp-3.14.7/README create mode 100644 doc/licenses/okhttp-3.14.7/dep-coordinates.txt create mode 100644 doc/licenses/okio-1.17.2/README create mode 100644 doc/licenses/okio-1.17.2/dep-coordinates.txt create mode 100644 doc/licenses/retrofit-2.7.2/README create mode 100644 doc/licenses/retrofit-2.7.2/dep-coordinates.txt create mode 100644 doc/licenses/rxjava-1.3.8/LICENSE create mode 100644 doc/licenses/rxjava-1.3.8/README create mode 100644 doc/licenses/rxjava-1.3.8/dep-coordinates.txt create mode 100644 doc/licenses/snakeyaml-1.27/README create mode 100644 doc/licenses/snakeyaml-1.27/dep-coordinates.txt create mode 100644 doc/licenses/stephenc-jcip-annotations-1.0-1/README create mode 100644 doc/licenses/stephenc-jcip-annotations-1.0-1/dep-coordinates.txt create mode 100644 extensions/guacamole-auth-vault/.ratignore create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/.ratignore create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/pom.xml create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProvider.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultAuthenticationException.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultCredentials.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/resources/guac-manifest.json create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/.ratignore create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/pom.xml create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProvider.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProviderModule.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/conf/VaultConfigurationService.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContextFactory.java create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/resources/translations/en.json create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/.ratignore create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/pom.xml create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/src/main/assembly/dist.xml create mode 100644 extensions/guacamole-auth-vault/pom.xml diff --git a/doc/licenses/adal4j-1.6.7/LICENSE b/doc/licenses/adal4j-1.6.7/LICENSE new file mode 100644 index 000000000..48bc6bb49 --- /dev/null +++ b/doc/licenses/adal4j-1.6.7/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/licenses/adal4j-1.6.7/README b/doc/licenses/adal4j-1.6.7/README new file mode 100644 index 000000000..34062836d --- /dev/null +++ b/doc/licenses/adal4j-1.6.7/README @@ -0,0 +1,8 @@ +adal4j (https://github.com/AzureAD/azure-activedirectory-library-for-java) +-------------------------------------------------------------------------- + + Version: 1.6.7 + From: 'Microsoft Corporation' (https://microsoft.com/) + License(s): + MIT (bundled/adal4j-1.6.7/LICENSE) + diff --git a/doc/licenses/adal4j-1.6.7/dep-coordinates.txt b/doc/licenses/adal4j-1.6.7/dep-coordinates.txt new file mode 100644 index 000000000..e8e0f3b2f --- /dev/null +++ b/doc/licenses/adal4j-1.6.7/dep-coordinates.txt @@ -0,0 +1 @@ +com.microsoft.azure:adal4j:jar:1.6.7 diff --git a/doc/licenses/apache-commons-lang-3.8.1/NOTICE b/doc/licenses/apache-commons-lang-3.8.1/NOTICE new file mode 100644 index 000000000..0f4ac594a --- /dev/null +++ b/doc/licenses/apache-commons-lang-3.8.1/NOTICE @@ -0,0 +1,5 @@ +Apache Commons Lang +Copyright 2001-2018 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/doc/licenses/apache-commons-lang-3.8.1/README b/doc/licenses/apache-commons-lang-3.8.1/README new file mode 100644 index 000000000..d8cf381ef --- /dev/null +++ b/doc/licenses/apache-commons-lang-3.8.1/README @@ -0,0 +1,8 @@ +Apache Commons Lang (http://commons.apache.org/proper/commons-lang/) +-------------------------------------------------------------------- + + Version: 3.8.1 + From: 'Apache Software Foundation' (https://www.apache.org/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/apache-commons-lang-3.8.1/dep-coordinates.txt b/doc/licenses/apache-commons-lang-3.8.1/dep-coordinates.txt new file mode 100644 index 000000000..f3305d051 --- /dev/null +++ b/doc/licenses/apache-commons-lang-3.8.1/dep-coordinates.txt @@ -0,0 +1 @@ +org.apache.commons:commons-lang3:jar:3.8.1 diff --git a/doc/licenses/asm-8.0.1/LICENSE.txt b/doc/licenses/asm-8.0.1/LICENSE.txt new file mode 100644 index 000000000..4d191851a --- /dev/null +++ b/doc/licenses/asm-8.0.1/LICENSE.txt @@ -0,0 +1,28 @@ + + ASM: a very small and fast Java bytecode manipulation framework + Copyright (c) 2000-2011 INRIA, France Telecom + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/licenses/asm-8.0.1/README b/doc/licenses/asm-8.0.1/README new file mode 100644 index 000000000..304b95281 --- /dev/null +++ b/doc/licenses/asm-8.0.1/README @@ -0,0 +1,8 @@ +ASM (https://asm.ow2.io/) +------------------------- + + Version: 8.0.1 + From: 'INRIA, France Telecom' + License(s): + BSD 3-clause (bundled/asm-8.0.1/LICENSE.txt) + diff --git a/doc/licenses/asm-8.0.1/dep-coordinates.txt b/doc/licenses/asm-8.0.1/dep-coordinates.txt new file mode 100644 index 000000000..cf52dc1a7 --- /dev/null +++ b/doc/licenses/asm-8.0.1/dep-coordinates.txt @@ -0,0 +1 @@ +org.ow2.asm:asm:jar:8.0.1 diff --git a/doc/licenses/autorest-client-runtime-1.7.4/LICENSE b/doc/licenses/autorest-client-runtime-1.7.4/LICENSE new file mode 100644 index 000000000..4918d653b --- /dev/null +++ b/doc/licenses/autorest-client-runtime-1.7.4/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Microsoft Azure + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/licenses/autorest-client-runtime-1.7.4/README b/doc/licenses/autorest-client-runtime-1.7.4/README new file mode 100644 index 000000000..e8a65f14f --- /dev/null +++ b/doc/licenses/autorest-client-runtime-1.7.4/README @@ -0,0 +1,9 @@ +AutoRest Client Runtimes for Java +(https://github.com/Azure/autorest-clientruntime-for-java) +---------------------------------------------------------- + + Version: 1.7.4 + From: 'Microsoft Azure' (https://azure.microsoft.com/) + License(s): + MIT (bundled/autorest-client-runtime-1.7.4/LICENSE) + diff --git a/doc/licenses/autorest-client-runtime-1.7.4/dep-coordinates.txt b/doc/licenses/autorest-client-runtime-1.7.4/dep-coordinates.txt new file mode 100644 index 000000000..5d1dc913f --- /dev/null +++ b/doc/licenses/autorest-client-runtime-1.7.4/dep-coordinates.txt @@ -0,0 +1,2 @@ +com.microsoft.rest:client-runtime:jar:1.7.4 +com.microsoft.azure:azure-client-runtime:jar:1.7.4 diff --git a/doc/licenses/azure-annotations-1.10.0/License.txt b/doc/licenses/azure-annotations-1.10.0/License.txt new file mode 100644 index 000000000..fbe8e19b3 --- /dev/null +++ b/doc/licenses/azure-annotations-1.10.0/License.txt @@ -0,0 +1,28 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ + +NOTE: The above has been extracted from the source of the "azure-annotations" +library, as may be downloaded from Maven Central: + +https://search.maven.org/remotecontent?filepath=com/microsoft/azure/azure-annotations/1.10.0/azure-annotations-1.10.0-sources.jar + +Unfortunately, the "License.txt" file noted is not included with the source +.jar, and the GitHub repository referenced by the pom.xml of +"azure-annotations" is not publicly visible: + +https://github.com/Microsoft/java-api-annotations + +I (Mike Jumper) have reached out to Microsoft to correct this and to request a +copy of the "License.txt" file if access to this repository cannot be fixed in +the near future. Until then, the above should serve as reasonable confirmation +that this library is indeed (1) licensed under the MIT license and (2) +copyright Microsoft Corporation. + +For reference, the terms of the open source license widely known as the "MIT +license" can be found here: + +https://opensource.org/licenses/MIT + diff --git a/doc/licenses/azure-annotations-1.10.0/README b/doc/licenses/azure-annotations-1.10.0/README new file mode 100644 index 000000000..183f0f76e --- /dev/null +++ b/doc/licenses/azure-annotations-1.10.0/README @@ -0,0 +1,9 @@ +Microsoft Azure SDK Annotations +(https://github.com/Microsoft/java-api-annotations) +--------------------------------------------------- + + Version: 1.10.0 + From: 'Microsoft Corporation' (https://microsoft.com/) + License(s): + MIT (bundled/azure-annotations-1.10.0/License.txt) + diff --git a/doc/licenses/azure-annotations-1.10.0/dep-coordinates.txt b/doc/licenses/azure-annotations-1.10.0/dep-coordinates.txt new file mode 100644 index 000000000..f96581d3c --- /dev/null +++ b/doc/licenses/azure-annotations-1.10.0/dep-coordinates.txt @@ -0,0 +1 @@ +com.microsoft.azure:azure-annotations:jar:1.10.0 diff --git a/doc/licenses/azure-sdk-for-java-1.2.4/LICENSE.txt b/doc/licenses/azure-sdk-for-java-1.2.4/LICENSE.txt new file mode 100644 index 000000000..49d21669a --- /dev/null +++ b/doc/licenses/azure-sdk-for-java-1.2.4/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/doc/licenses/azure-sdk-for-java-1.2.4/README b/doc/licenses/azure-sdk-for-java-1.2.4/README new file mode 100644 index 000000000..9e6e3fd08 --- /dev/null +++ b/doc/licenses/azure-sdk-for-java-1.2.4/README @@ -0,0 +1,8 @@ +Azure SDK for Java (https://github.com/Azure/azure-sdk-for-java/) +----------------------------------------------------------------- + + Version: 1.2.4 + From: 'Microsoft' (https://microsoft.com/) + License(s): + MIT (bundled/azure-sdk-for-java-1.2.4/LICENSE.txt) + diff --git a/doc/licenses/azure-sdk-for-java-1.2.4/dep-coordinates.txt b/doc/licenses/azure-sdk-for-java-1.2.4/dep-coordinates.txt new file mode 100644 index 000000000..9bfa04c27 --- /dev/null +++ b/doc/licenses/azure-sdk-for-java-1.2.4/dep-coordinates.txt @@ -0,0 +1,5 @@ +com.microsoft.azure:azure-keyvault-core:jar:1.2.4 +com.microsoft.azure:azure-keyvault-cryptography:jar:1.2.4 +com.microsoft.azure:azure-keyvault-webkey:jar:1.2.4 +com.microsoft.azure:azure-keyvault:jar:1.2.4 + diff --git a/doc/licenses/gson-2.8.0/README b/doc/licenses/gson-2.8.0/README new file mode 100644 index 000000000..40530bbf6 --- /dev/null +++ b/doc/licenses/gson-2.8.0/README @@ -0,0 +1,8 @@ +Gson (https://github.com/google/gson) +------------------------------------- + + Version: 2.8.0 + From: 'Google Inc.' (http://www.google.com/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/gson-2.8.0/dep-coordinates.txt b/doc/licenses/gson-2.8.0/dep-coordinates.txt new file mode 100644 index 000000000..d171937b9 --- /dev/null +++ b/doc/licenses/gson-2.8.0/dep-coordinates.txt @@ -0,0 +1 @@ +com.google.code.gson:gson:jar:2.8.0 diff --git a/doc/licenses/jackson-2.13.1/dep-coordinates.txt b/doc/licenses/jackson-2.13.1/dep-coordinates.txt index f2cbd8d67..f469e53ff 100644 --- a/doc/licenses/jackson-2.13.1/dep-coordinates.txt +++ b/doc/licenses/jackson-2.13.1/dep-coordinates.txt @@ -2,4 +2,5 @@ com.fasterxml.jackson.core:jackson-databind:jar:2.13.1 com.fasterxml.jackson.core:jackson-core:jar:2.13.1 com.fasterxml.jackson.core:jackson-annotations:jar:2.13.1 com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.13.1 +com.fasterxml.jackson.datatype:jackson-datatype-joda:jar:2.13.1 com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.13.1 diff --git a/doc/licenses/joda-time-2.10.8/NOTICE b/doc/licenses/joda-time-2.10.8/NOTICE new file mode 100644 index 000000000..b8f54d7b2 --- /dev/null +++ b/doc/licenses/joda-time-2.10.8/NOTICE @@ -0,0 +1,2 @@ +This product includes software developed by +Joda.org (https://www.joda.org/). diff --git a/doc/licenses/joda-time-2.10.8/README b/doc/licenses/joda-time-2.10.8/README new file mode 100644 index 000000000..ee8d28c96 --- /dev/null +++ b/doc/licenses/joda-time-2.10.8/README @@ -0,0 +1,8 @@ +Joda-Time (https://www.joda.org/joda-time/) +---------------------------------------------- + + Version: 2.10.8 + From: 'Joda.org' (https://www.joda.org/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/joda-time-2.10.8/dep-coordinates.txt b/doc/licenses/joda-time-2.10.8/dep-coordinates.txt new file mode 100644 index 000000000..0cc75bc83 --- /dev/null +++ b/doc/licenses/joda-time-2.10.8/dep-coordinates.txt @@ -0,0 +1 @@ +joda-time:joda-time:jar:2.10.8 diff --git a/doc/licenses/json-smart-2.4.2/LICENSE b/doc/licenses/json-smart-2.4.2/LICENSE new file mode 100644 index 000000000..8f71f43fe --- /dev/null +++ b/doc/licenses/json-smart-2.4.2/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/doc/licenses/json-smart-2.4.2/README b/doc/licenses/json-smart-2.4.2/README new file mode 100644 index 000000000..6ba0bb66a --- /dev/null +++ b/doc/licenses/json-smart-2.4.2/README @@ -0,0 +1,8 @@ +json-smart (https://netplex.github.io/json-smart/) +-------------------------------------------------- + + Version: 2.4.2 + From: 'Uriel Chemouni' (https://github.com/UrielCh) + License(s): + Apache v2.0 + diff --git a/doc/licenses/json-smart-2.4.2/dep-coordinates.txt b/doc/licenses/json-smart-2.4.2/dep-coordinates.txt new file mode 100644 index 000000000..bf1c8b44b --- /dev/null +++ b/doc/licenses/json-smart-2.4.2/dep-coordinates.txt @@ -0,0 +1,2 @@ +net.minidev:accessors-smart:jar:2.4.2 +net.minidev:json-smart:jar:2.4.2 diff --git a/doc/licenses/lang-tag-1.5/README b/doc/licenses/lang-tag-1.5/README new file mode 100644 index 000000000..2de3b0e74 --- /dev/null +++ b/doc/licenses/lang-tag-1.5/README @@ -0,0 +1,8 @@ +Nimbus Language Tags (https://bitbucket.org/connect2id/nimbus-language-tags) +---------------------------------------------------------------------------- + + Version: 1.5 + From: 'Connect2id Ltd.' (https://connect2id.com/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/lang-tag-1.5/dep-coordinates.txt b/doc/licenses/lang-tag-1.5/dep-coordinates.txt new file mode 100644 index 000000000..fd885a12b --- /dev/null +++ b/doc/licenses/lang-tag-1.5/dep-coordinates.txt @@ -0,0 +1 @@ +com.nimbusds:lang-tag:jar:1.5 diff --git a/doc/licenses/nimbus-content-type-2.1/README b/doc/licenses/nimbus-content-type-2.1/README new file mode 100644 index 000000000..13b925e21 --- /dev/null +++ b/doc/licenses/nimbus-content-type-2.1/README @@ -0,0 +1,8 @@ +Nimbus Content Type (https://bitbucket.org/connect2id/nimbus-content-type) +-------------------------------------------------------------------------- + + Version: 2.1 + From: 'Connect2id Ltd.' (https://connect2id.com/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/nimbus-content-type-2.1/dep-coordinates.txt b/doc/licenses/nimbus-content-type-2.1/dep-coordinates.txt new file mode 100644 index 000000000..8910f18d2 --- /dev/null +++ b/doc/licenses/nimbus-content-type-2.1/dep-coordinates.txt @@ -0,0 +1 @@ +com.nimbusds:content-type:jar:2.1 diff --git a/doc/licenses/nimbus-jose-jwt-9.8.1/README b/doc/licenses/nimbus-jose-jwt-9.8.1/README new file mode 100644 index 000000000..5035f4127 --- /dev/null +++ b/doc/licenses/nimbus-jose-jwt-9.8.1/README @@ -0,0 +1,8 @@ +Nimbus JOSE+JWT (https://bitbucket.org/connect2id/nimbus-jose-jwt) +------------------------------------------------------------------ + + Version: 9.8.1 + From: 'Connect2id Ltd.' (https://connect2id.com/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/nimbus-jose-jwt-9.8.1/dep-coordinates.txt b/doc/licenses/nimbus-jose-jwt-9.8.1/dep-coordinates.txt new file mode 100644 index 000000000..d15ff6d87 --- /dev/null +++ b/doc/licenses/nimbus-jose-jwt-9.8.1/dep-coordinates.txt @@ -0,0 +1 @@ +com.nimbusds:nimbus-jose-jwt:jar:9.8.1 diff --git a/doc/licenses/oauth2-oidc-sdk-9.4/README b/doc/licenses/oauth2-oidc-sdk-9.4/README new file mode 100644 index 000000000..f32808a0e --- /dev/null +++ b/doc/licenses/oauth2-oidc-sdk-9.4/README @@ -0,0 +1,9 @@ +Nimbus OAuth 2.0 SDK with OpenID Connect extensions +(https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions) +------------------------------------------------------------------------------- + + Version: 9.4 + From: 'Connect2id Ltd.' (https://connect2id.com/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/oauth2-oidc-sdk-9.4/dep-coordinates.txt b/doc/licenses/oauth2-oidc-sdk-9.4/dep-coordinates.txt new file mode 100644 index 000000000..2bce0c919 --- /dev/null +++ b/doc/licenses/oauth2-oidc-sdk-9.4/dep-coordinates.txt @@ -0,0 +1 @@ +com.nimbusds:oauth2-oidc-sdk:jar:9.4 diff --git a/doc/licenses/okhttp-3.14.7/README b/doc/licenses/okhttp-3.14.7/README new file mode 100644 index 000000000..c3bd4173b --- /dev/null +++ b/doc/licenses/okhttp-3.14.7/README @@ -0,0 +1,8 @@ +OkHttp (https://github.com/square/okhttp) +----------------------------------------- + + Version: 3.14.7 + From: 'Square, Inc.' (http://square.github.io/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/okhttp-3.14.7/dep-coordinates.txt b/doc/licenses/okhttp-3.14.7/dep-coordinates.txt new file mode 100644 index 000000000..9729cd760 --- /dev/null +++ b/doc/licenses/okhttp-3.14.7/dep-coordinates.txt @@ -0,0 +1,3 @@ +com.squareup.okhttp3:logging-interceptor:jar:3.14.7 +com.squareup.okhttp3:okhttp-urlconnection:jar:3.14.7 +com.squareup.okhttp3:okhttp:jar:3.14.7 diff --git a/doc/licenses/okio-1.17.2/README b/doc/licenses/okio-1.17.2/README new file mode 100644 index 000000000..13471ad46 --- /dev/null +++ b/doc/licenses/okio-1.17.2/README @@ -0,0 +1,8 @@ +Okio (https://github.com/square/okio) +------------------------------------- + + Version: 1.17.2 + From: 'Square, Inc.' (http://square.github.io/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/okio-1.17.2/dep-coordinates.txt b/doc/licenses/okio-1.17.2/dep-coordinates.txt new file mode 100644 index 000000000..54ab8297c --- /dev/null +++ b/doc/licenses/okio-1.17.2/dep-coordinates.txt @@ -0,0 +1 @@ +com.squareup.okio:okio:jar:1.17.2 diff --git a/doc/licenses/retrofit-2.7.2/README b/doc/licenses/retrofit-2.7.2/README new file mode 100644 index 000000000..83fea9db2 --- /dev/null +++ b/doc/licenses/retrofit-2.7.2/README @@ -0,0 +1,8 @@ +Retrofit (https://github.com/square/retrofit) +--------------------------------------------- + + Version: 2.7.2 + From: 'Square, Inc.' (http://square.github.io/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/retrofit-2.7.2/dep-coordinates.txt b/doc/licenses/retrofit-2.7.2/dep-coordinates.txt new file mode 100644 index 000000000..ff175a09d --- /dev/null +++ b/doc/licenses/retrofit-2.7.2/dep-coordinates.txt @@ -0,0 +1,3 @@ +com.squareup.retrofit2:adapter-rxjava:jar:2.7.2 +com.squareup.retrofit2:converter-jackson:jar:2.7.2 +com.squareup.retrofit2:retrofit:jar:2.7.2 diff --git a/doc/licenses/rxjava-1.3.8/LICENSE b/doc/licenses/rxjava-1.3.8/LICENSE new file mode 100644 index 000000000..7f8ced0d1 --- /dev/null +++ b/doc/licenses/rxjava-1.3.8/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2012 Netflix, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/licenses/rxjava-1.3.8/README b/doc/licenses/rxjava-1.3.8/README new file mode 100644 index 000000000..9aaa62d61 --- /dev/null +++ b/doc/licenses/rxjava-1.3.8/README @@ -0,0 +1,8 @@ +RxJava – Reactive Extensions for the JVM (https://github.com/ReactiveX/RxJava) +------------------------------------------------------------------------------ + + Version: 1.3.8 + From: 'RxJava Contributors' (https://github.com/ReactiveX/RxJava) + License(s): + Apache v2.0 + diff --git a/doc/licenses/rxjava-1.3.8/dep-coordinates.txt b/doc/licenses/rxjava-1.3.8/dep-coordinates.txt new file mode 100644 index 000000000..e17a77560 --- /dev/null +++ b/doc/licenses/rxjava-1.3.8/dep-coordinates.txt @@ -0,0 +1 @@ +io.reactivex:rxjava:jar:1.3.8 diff --git a/doc/licenses/snakeyaml-1.27/README b/doc/licenses/snakeyaml-1.27/README new file mode 100644 index 000000000..3fcd837d6 --- /dev/null +++ b/doc/licenses/snakeyaml-1.27/README @@ -0,0 +1,8 @@ +SnakeYAML (https://bitbucket.org/asomov/snakeyaml/) +--------------------------------------------------- + + Version: 1.27 + From: 'Andrey Somov' (https://bitbucket.org/asomov/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/snakeyaml-1.27/dep-coordinates.txt b/doc/licenses/snakeyaml-1.27/dep-coordinates.txt new file mode 100644 index 000000000..d7cbad91a --- /dev/null +++ b/doc/licenses/snakeyaml-1.27/dep-coordinates.txt @@ -0,0 +1 @@ +org.yaml:snakeyaml:jar:1.27 diff --git a/doc/licenses/stephenc-jcip-annotations-1.0-1/README b/doc/licenses/stephenc-jcip-annotations-1.0-1/README new file mode 100644 index 000000000..8e59938a1 --- /dev/null +++ b/doc/licenses/stephenc-jcip-annotations-1.0-1/README @@ -0,0 +1,8 @@ +Clean-room JCIP Annotations (https://github.com/stephenc/jcip-annotations) +-------------------------------------------------------------------------- + + Version: 1.0-1 + From: 'Stephen Connolly' (https://github.com/stephenc) + License(s): + Apache v2.0 + diff --git a/doc/licenses/stephenc-jcip-annotations-1.0-1/dep-coordinates.txt b/doc/licenses/stephenc-jcip-annotations-1.0-1/dep-coordinates.txt new file mode 100644 index 000000000..e42206c27 --- /dev/null +++ b/doc/licenses/stephenc-jcip-annotations-1.0-1/dep-coordinates.txt @@ -0,0 +1 @@ +com.github.stephenc.jcip:jcip-annotations:jar:1.0-1 diff --git a/extensions/guacamole-auth-vault/.ratignore b/extensions/guacamole-auth-vault/.ratignore new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/.ratignore b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/.ratignore new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/pom.xml b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/pom.xml new file mode 100644 index 000000000..d1d336aa1 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/pom.xml @@ -0,0 +1,194 @@ + + + + + 4.0.0 + org.apache.guacamole + guacamole-auth-vault-azure + jar + 1.4.0 + guacamole-auth-vault-azure + http://guacamole.apache.org/ + + + 1.7.4 + 3.14.7 + + + + org.apache.guacamole + guacamole-auth-vault + 1.4.0 + ../../ + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + false + + + + + + + + + + + org.apache.guacamole + guacamole-ext + provided + + + + + org.apache.guacamole + guacamole-auth-vault-base + 1.4.0 + + + + + com.microsoft.azure + azure-keyvault + 1.2.4 + + + + + org.slf4j + slf4j-api + + + + + com.microsoft.azure + azure-client-runtime + + + commons-codec + commons-codec + + + + + + + + + com.microsoft.azure + adal4j + 1.6.7 + + + + + org.slf4j + slf4j-api + + + + + org.apache.commons + commons-lang3 + + + + + + + + com.microsoft.azure + azure-client-runtime + ${azure-client-runtimes.version} + + + com.microsoft.rest + client-runtime + ${azure-client-runtimes.version} + + + org.apache.commons + commons-lang3 + + + com.squareup.okhttp3 + okhttp + + + com.squareup.okhttp3 + okhttp-urlconnection + + + com.squareup.okhttp3 + logging-interceptor + + + + + + + org.apache.commons + commons-lang3 + 3.8.1 + + + commons-codec + commons-codec + 1.14 + + + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + com.squareup.okhttp3 + okhttp-urlconnection + ${okhttp.version} + + + com.squareup.okhttp3 + logging-interceptor + ${okhttp.version} + + + + + diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProvider.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProvider.java new file mode 100644 index 000000000..5fd091374 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProvider.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.azure; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.vault.VaultAuthenticationProvider; + +/** + * VaultAuthenticationProvider implementation which reads secrets from Azure + * Key Vault. + */ +public class AzureKeyVaultAuthenticationProvider extends VaultAuthenticationProvider { + + /** + * Creates a new AzureKeyVaultAuthenticationProvider which reads secrets + * from a configured Azure Key Vault. + * + * @throws GuacamoleException + * If configuration details cannot be read from guacamole.properties. + */ + public AzureKeyVaultAuthenticationProvider() throws GuacamoleException { + super(new AzureKeyVaultAuthenticationProviderModule()); + } + + @Override + public String getIdentifier() { + return "azure-keyvault"; + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java new file mode 100644 index 000000000..3cba8b705 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.azure; + +import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.vault.VaultAuthenticationProviderModule; +import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultConfigurationService; +import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultCredentials; +import org.apache.guacamole.auth.vault.azure.secret.AzureKeyVaultSecretService; +import org.apache.guacamole.auth.vault.conf.VaultConfigurationService; +import org.apache.guacamole.auth.vault.secret.VaultSecretService; + +/** + * Guice module which configures injections specific to Azure Key Vault + * support. + */ +public class AzureKeyVaultAuthenticationProviderModule + extends VaultAuthenticationProviderModule { + + /** + * Creates a new AzureKeyVaultAuthenticationiProviderModule which + * configures dependency injection for the Azure Key Vault authentication + * provider and related services. + * + * @throws GuacamoleException + * If configuration details in guacamole.properties cannot be parsed. + */ + public AzureKeyVaultAuthenticationProviderModule() throws GuacamoleException {} + + @Override + protected void configureVault() { + + // Bind services specific to Azure Key Vault + bind(VaultConfigurationService.class).to(AzureKeyVaultConfigurationService.class); + bind(VaultSecretService.class).to(AzureKeyVaultSecretService.class); + + // Bind ADAL credentials implementation required for authenticating + // against Azure + bind(KeyVaultCredentials.class).to(AzureKeyVaultCredentials.class); + + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultAuthenticationException.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultAuthenticationException.java new file mode 100644 index 000000000..5cf92a1b6 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultAuthenticationException.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.azure.conf; + +/** + * Unchecked exception thrown by AzureKeyVaultCredentials if an error occurs + * during the authentication process. Note that the base KeyVaultCredentials + * base class does not provide for checked exceptions within the authentication + * process. + * + * @see AzureKeyVaultCredentials#doAuthenticate(java.lang.String, java.lang.String, java.lang.String) + */ +public class AzureKeyVaultAuthenticationException extends RuntimeException { + + /** + * Creates a new AzureKeyVaultAuthenticationException having the given + * human-readable message. + * + * @param message + * A human-readable message describing the error that occurred. + */ + public AzureKeyVaultAuthenticationException(String message) { + super(message); + } + + /** + * Creates a new AzureKeyVaultAuthenticationException having the given + * human-readable message and cause. + * + * @param message + * A human-readable message describing the error that occurred. + * + * @param cause + * The error that caused this exception. + */ + public AzureKeyVaultAuthenticationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java new file mode 100644 index 000000000..2be7bd1b0 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.azure.conf; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.microsoft.aad.adal4j.ClientCredential; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.vault.conf.VaultConfigurationService; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.StringGuacamoleProperty; + +/** + * Service for retrieving configuration information regarding the Azure Key + * Vault authentication extension. + */ +@Singleton +public class AzureKeyVaultConfigurationService extends VaultConfigurationService { + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /** + * The name of the file which contains the JSON mapping of connection + * parameter token to Azure Key Vault secret name. + */ + private static final String TOKEN_MAPPING_FILENAME = "azure-keyvault-token-mapping.json"; + + /** + * The URL of the Azure Key Vault that should be used to populate token + * values. + */ + private static final StringGuacamoleProperty VAULT_URL = new StringGuacamoleProperty() { + + @Override + public String getName() { + return "azure-keyvault-url"; + } + + }; + + /** + * The client ID that should be used to authenticate with Azure Key Vault + * using ADAL. + */ + private static final StringGuacamoleProperty CLIENT_ID = new StringGuacamoleProperty() { + + @Override + public String getName() { + return "azure-keyvault-client-id"; + } + + }; + + /** + * The client key that should be used to authenticate with Azure Key Vault + * using ADAL. + */ + private static final StringGuacamoleProperty CLIENT_KEY = new StringGuacamoleProperty() { + + @Override + public String getName() { + return "azure-keyvault-client-key"; + } + + }; + + /** + * Creates a new AzureKeyVaultConfigurationService which reads the token + * mapping from "azure-keyvault-token-mapping.json". The token mapping is + * a JSON file which lists each connection parameter token and the name of + * the secret from which the value for that token should be read. + */ + public AzureKeyVaultConfigurationService() { + super(TOKEN_MAPPING_FILENAME); + } + + /** + * Returns the base URL of the Azure Key Vault containing the secrets that + * should be retrieved to populate connection parameter tokens. The base + * URL is specified with the "azure-keyvault-url" property. + * + * @return + * The base URL of the Azure Key Vault. + * + * @throws GuacamoleException + * If the base URL is not specified within guacamole.properties. + */ + public String getVaultURL() throws GuacamoleException { + return environment.getRequiredProperty(VAULT_URL); + } + + /** + * Returns the credentials that should be used to authenticate with Azure + * Key Vault when retrieving secrets. Azure's "ADAL" authentication will be + * used, requiring a client ID and key. These values are specified with the + * "azure-keyvault-client-id" and "azure-keyvault-client-key" properties + * respectively. + * + * @return + * The credentials that should be used to authenticate with Azure Key + * Vault. + * + * @throws GuacamoleException + * If the client ID or key are not specified within + * guacamole.properties. + */ + public ClientCredential getClientCredentials() throws GuacamoleException { + return new ClientCredential( + environment.getRequiredProperty(CLIENT_ID), + environment.getRequiredProperty(CLIENT_KEY) + ); + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultCredentials.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultCredentials.java new file mode 100644 index 000000000..c69da5f1e --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultCredentials.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.azure.conf; + +import com.google.inject.Inject; +import com.microsoft.aad.adal4j.AuthenticationContext; +import com.microsoft.aad.adal4j.AuthenticationResult; +import com.microsoft.aad.adal4j.ClientCredential; +import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; +import java.net.MalformedURLException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.apache.guacamole.GuacamoleException; + +/** + * KeyVaultCredentials implementation which retrieves the required client ID + * and key from guacamole.properties. Note that KeyVaultCredentials as + * implemented in the Azure Java SDK is NOT THREADSAFE; it leverages a + * non-concurrent HashMap for authentication result caching and does not + * perform any synchronization. + */ +public class AzureKeyVaultCredentials extends KeyVaultCredentials { + + /** + * Service for retrieving configuration information. + */ + @Inject + private AzureKeyVaultConfigurationService confService; + + /** + * {@inheritDoc} + * + * @throws AzureKeyVaultAuthenticationException + * If an error occurs preventing successful authentication. Note that + * this exception is unchecked. Uses of this class which need to be + * aware of errors in the authentication process must manually catch + * this exception. + */ + @Override + public String doAuthenticate(String authorization, String resource, + String scope) throws AzureKeyVaultAuthenticationException { + + // Read Azure credentials from guacamole.properties + ClientCredential credentials; + try { + credentials = confService.getClientCredentials(); + } + catch (GuacamoleException e) { + throw new AzureKeyVaultAuthenticationException("Azure " + + "credentials could not be read.", e); + } + + ExecutorService service = Executors.newFixedThreadPool(1); + try { + + // Attempt to aquire authentication token from Azure + AuthenticationContext context = new AuthenticationContext(authorization, false, service); + Future future = context.acquireToken(resource, credentials, null); + + // Wait for response + AuthenticationResult result = future.get(); + + // The semantics of a null return value are not documented, however + // example code provided with the Azure Java SDK demonstrates that + // a null check is required, albeit without explanation + if (result == null) + throw new AzureKeyVaultAuthenticationException( + "Authentication result from Azure was empty."); + + // Return authentication token from successful response + return result.getAccessToken(); + + } + + // Rethrow any errors which occur during the authentication process as + // AzureKeyVaultAuthenticationExceptions + catch (MalformedURLException e) { + throw new AzureKeyVaultAuthenticationException("Azure " + + "authentication URL is malformed.", e); + } + catch (InterruptedException e) { + throw new AzureKeyVaultAuthenticationException("Azure " + + "authentication process was interrupted.", e); + } + catch (ExecutionException e) { + throw new AzureKeyVaultAuthenticationException("Authentication " + + "against Azure failed.", e); + } + + finally { + service.shutdown(); + } + + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java new file mode 100644 index 000000000..ccbd6c9cc --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.azure.secret; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.microsoft.azure.keyvault.KeyVaultClient; +import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; +import com.microsoft.azure.keyvault.models.SecretBundle; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultAuthenticationException; +import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultConfigurationService; +import org.apache.guacamole.auth.vault.secret.VaultSecretService; + +/** + * Service which retrieves secrets from Azure Key Vault. + */ +@Singleton +public class AzureKeyVaultSecretService implements VaultSecretService { + + /** + * Pattern which matches contiguous groups of characters which are not + * allowed within Azure Key Vault secret names. + */ + private static final Pattern DISALLOWED_CHARACTERS = Pattern.compile("[^a-zA-Z0-9-]+"); + + /** + * Service for retrieving configuration information. + */ + @Inject + private AzureKeyVaultConfigurationService confService; + + /** + * Provider for Azure Key Vault credentials. + */ + @Inject + private Provider credentialProvider; + + /** + * {@inheritDoc} + * + *

Azure Key Vault allows strictly a-z, A-Z, 0-9, and "-". This + * implementation strips out all contiguous groups of characters which are + * not allowed by Azure Key Vault, replacing them with a single dash. + */ + @Override + public String canonicalize(String name) { + Matcher disallowed = DISALLOWED_CHARACTERS.matcher(name); + return disallowed.replaceAll("-"); + } + + @Override + public String getValue(String name) throws GuacamoleException { + + try { + + // Retrieve configuration information necessary for connecting to + // Azure Key Vault + String url = confService.getVaultURL(); + KeyVaultCredentials credentials = credentialProvider.get(); + + // Authenticate against Azure Key Vault + KeyVaultClient client = new KeyVaultClient(credentials); + + // Retrieve requested secret + SecretBundle secret = client.getSecret(url, name); + + // FIXME: STUB + return null; + + } + catch (AzureKeyVaultAuthenticationException e) { + throw new GuacamoleServerException("Unable to authenticate with Azure.", e); + } + + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..87e1b1165 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/resources/guac-manifest.json @@ -0,0 +1,16 @@ +{ + + "guacamoleVersion" : "1.4.0", + + "name" : "Azure Key Vault", + "namespace" : "azure-keyvault", + + "authProviders" : [ + "org.apache.guacamole.auth.vault.azure.AzureKeyVaultAuthenticationProvider" + ], + + "translations" : [ + "translations/en.json" + ] + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/.ratignore b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/.ratignore new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/pom.xml b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/pom.xml new file mode 100644 index 000000000..d59754fe6 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/pom.xml @@ -0,0 +1,70 @@ + + + + + 4.0.0 + org.apache.guacamole + guacamole-auth-vault-base + jar + guacamole-auth-vault-base + http://guacamole.apache.org/ + + + UTF-8 + + + + org.apache.guacamole + guacamole-auth-vault + 1.4.0 + ../../ + + + + + + + org.apache.guacamole + guacamole-ext + provided + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.google.inject + guice + + + com.google.inject.extensions + guice-assistedinject + + + + + diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProvider.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProvider.java new file mode 100644 index 000000000..0b9126a67 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProvider.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.vault.user.VaultUserContextFactory; +import org.apache.guacamole.net.auth.AbstractAuthenticationProvider; +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.net.auth.UserContext; + +/** + * AuthenticationProvider implementation which automatically injects tokens + * containing the values of secrets retrieved from a vault. + */ +public abstract class VaultAuthenticationProvider + extends AbstractAuthenticationProvider { + + /** + * Factory for creating instances of the relevant vault-specific + * UserContext implementation. + */ + private final VaultUserContextFactory userContextFactory; + + /** + * Creates a new VaultAuthenticationProvider which uses the given module to + * configure dependency injection. + * + * @param module + * The module to use to configure dependency injection. + */ + protected VaultAuthenticationProvider(VaultAuthenticationProviderModule module) { + Injector injector = Guice.createInjector(module); + this.userContextFactory = injector.getInstance(VaultUserContextFactory.class); + } + + @Override + public UserContext decorate(UserContext context, + AuthenticatedUser authenticatedUser, Credentials credentials) + throws GuacamoleException { + return userContextFactory.create(context); + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProviderModule.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProviderModule.java new file mode 100644 index 000000000..9e5ae7155 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProviderModule.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault; + +import com.google.inject.AbstractModule; +import com.google.inject.assistedinject.FactoryModuleBuilder; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.vault.user.VaultUserContext; +import org.apache.guacamole.auth.vault.user.VaultUserContextFactory; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.environment.LocalEnvironment; +import org.apache.guacamole.net.auth.UserContext; + +/** + * Guice module which configures injections specific to the base support for + * key vaults. When adding support for a key vault provider, a subclass + * specific to that vault implementation will need to be created. + * + * @see AzureKeyVaultAuthenticationProviderModule + */ +public abstract class VaultAuthenticationProviderModule extends AbstractModule { + + /** + * Guacamole server environment. + */ + private final Environment environment; + + /** + * Creates a new VaultAuthenticationProviderModule which configures + * dependency injection for the Azure Key Vault authentication provider. + * + * @throws GuacamoleException + * If an error occurs while retrieving the Guacamole server + * environment. + */ + public VaultAuthenticationProviderModule() throws GuacamoleException { + this.environment = LocalEnvironment.getInstance(); + } + + /** + * Configures injections for interfaces which are implementation-specific + * to the vault service in use. Subclasses MUST provide a version of this + * function which binds concrete implementations to the following + * interfaces: + * + * - VaultConfigurationService + * - VaultSecretService + * + * @see AzureKeyVaultAuthenticationProviderModule + */ + protected abstract void configureVault(); + + /** + * Returns the instance of the Guacamole server environment which will be + * exposed to other classes via dependency injection. + * + * @return + * The instance of the Guacamole server environment which will be + * exposed via dependency injection. + */ + protected Environment getEnvironment() { + return environment; + } + + @Override + protected void configure() { + + // Bind Guacamole server environment + bind(Environment.class).toInstance(environment); + + // Bind factory for creating UserContexts + install(new FactoryModuleBuilder() + .implement(UserContext.class, VaultUserContext.class) + .build(VaultUserContextFactory.class)); + + // Bind all other implementation-specific interfaces + configureVault(); + + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/conf/VaultConfigurationService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/conf/VaultConfigurationService.java new file mode 100644 index 000000000..9cafbd45f --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/conf/VaultConfigurationService.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.conf; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.auth.vault.VaultAuthenticationProviderModule; +import org.apache.guacamole.environment.Environment; + +/** + * Base class for services which retrieve key vault configuration information. + * A concrete implementation of this class must be defined and bound for key + * vault support to work. + * + * @see VaultAuthenticationProviderModule + */ +public abstract class VaultConfigurationService { + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /** + * ObjectMapper for deserializing JSON. + */ + private static final ObjectMapper mapper = new ObjectMapper(); + + /** + * The name of the file containing a JSON mapping of Guacamole parameter + * token to vault secret name. + */ + private final String tokenMappingFilename; + + /** + * Creates a new VaultConfigurationService which retrieves the token/secret + * mapping from a JSON file having the given name. + * + * @param tokenMappingFilename + * The name of the JSON file containing the token/secret mapping. + */ + protected VaultConfigurationService(String tokenMappingFilename) { + this.tokenMappingFilename = tokenMappingFilename; + } + + /** + * Returns a mapping dictating the name of the secret which maps to each + * parameter token. In the returned mapping, the value of each entry is the + * name of the secret to use to populate the value of the parameter token, + * and the key of each entry is the name of the parameter token which + * should receive the value of the secret. + * + * The name of the secret may contain its own tokens, which will be + * substituted using values from the given filter. See the definition of + * VaultUserContext for the names of these tokens and the contexts in which + * they can be applied to secret names. + * + * @return + * A mapping dictating the name of the secret which maps to each + * parameter token. + * + * @throws GuacamoleException + * If the JSON file defining the token/secret mapping cannot be read. + */ + public Map getTokenMapping() throws GuacamoleException { + + // Get configuration file from GUACAMOLE_HOME + File confFile = new File(environment.getGuacamoleHome(), tokenMappingFilename); + + // Deserialize token mapping from JSON + try { + return mapper.readValue(confFile, new TypeReference>() {}); + } + + // Fail if JSON is invalid/unreadable + catch (IOException e) { + throw new GuacamoleServerException("Unable to read token mapping " + + "configuration file \"" + tokenMappingFilename + "\".", e); + } + + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java new file mode 100644 index 000000000..49d0d9ce3 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.secret; + +import org.apache.guacamole.GuacamoleException; + +/** + * Generic service for retrieving the value of a secret stored in a vault. + */ +public interface VaultSecretService { + + /** + * Translates an arbitrary string, which may contain characters not allowed + * by the vault implementation, into a string which is a valid secret name. + * The type of transformation performed on the string, if any, will depend + * on the specific requirements of the vault provider. + * + * NOTE: It is critical that this transformation is deterministic and + * reasonably predictable for users. If an implementation must apply a + * transformation to secret names, that transformation needs to be + * documented. + * + * @param name + * An arbitrary string intended for use as a secret name, but which may + * contain characters not allowed by the vault implementation. + * + * @return + * A name containing essentially the same content as the provided + * string, but transformed deterministically such that it is acceptable + * as a secret name by the vault provider. + */ + String canonicalize(String name); + + /** + * Returns the value of the secret having the given name. If no such + * secret exists, null is returned. + * + * @param name + * The name of the secret to retrieve. + * + * @return + * The value of the secret having the given name, or null if no such + * secret exists. + * + * @throws GuacamoleException + * If the secret cannot be retrieved due to an error. + */ + String getValue(String name) throws GuacamoleException; + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java new file mode 100644 index 000000000..13cb6d515 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java @@ -0,0 +1,281 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.user; + +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.AssistedInject; +import java.util.HashMap; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.vault.conf.VaultConfigurationService; +import org.apache.guacamole.net.auth.Connection; +import org.apache.guacamole.net.auth.ConnectionGroup; +import org.apache.guacamole.net.auth.TokenInjectingUserContext; +import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.auth.vault.secret.VaultSecretService; +import org.apache.guacamole.protocol.GuacamoleConfiguration; +import org.apache.guacamole.token.GuacamoleTokenUndefinedException; +import org.apache.guacamole.token.TokenFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * UserContext implementation which automatically injects tokens containing the + * values of secrets retrieved from a vault. + */ +public class VaultUserContext extends TokenInjectingUserContext { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(VaultUserContext.class); + + /** + * The name of the token which will be replaced with the username of the + * current user if specified within the name of a secret. This token + * applies to both connections and connection groups. + */ + private static final String USERNAME_TOKEN = "GUAC_USERNAME"; + + /** + * The name of the token which will be replaced with the name of the + * current connection group if specified within the name of a secret. This + * token only applies only to connection groups. + */ + private static final String CONNECTION_GROUP_NAME_TOKEN = "CONNECTION_GROUP_NAME"; + + /** + * The name of the token which will be replaced with the identifier of the + * current connection group if specified within the name of a secret. This + * token only applies only to connection groups. + */ + private static final String CONNECTION_GROUP_IDENTIFIER_TOKEN = "CONNECTION_GROUP_ID"; + + /** + * The name of the token which will be replaced with the \"hostname\" + * connection parameter of the current connection if specified within the + * name of a secret. This token only applies only to connections. + */ + private static final String CONNECTION_HOSTNAME_TOKEN = "CONNECTION_HOSTNAME"; + + /** + * The name of the token which will be replaced with the \"username\" + * connection parameter of the current connection if specified within the + * name of a secret. This token only applies only to connections. + */ + private static final String CONNECTION_USERNAME_TOKEN = "CONNECTION_USERNAME"; + + /** + * The name of the token which will be replaced with the name of the + * current connection if specified within the name of a secret. This token + * only applies only to connections. + */ + private static final String CONNECTION_NAME_TOKEN = "CONNECTION_NAME"; + + /** + * The name of the token which will be replaced with the identifier of the + * current connection if specified within the name of a secret. This token + * only applies only to connections. + */ + private static final String CONNECTION_IDENTIFIER_TOKEN = "CONNECTION_ID"; + + /** + * Service for retrieving configuration information. + */ + @Inject + private VaultConfigurationService confService; + + /** + * Service for retrieving the values of secrets stored in a vault. + */ + @Inject + private VaultSecretService secretService; + + /** + * Creates a new VaultUserContext which automatically injects tokens + * containing values of secrets retrieved from a vault. The given + * UserContext is decorated such that connections and connection groups + * will receive additional tokens during the connection process. + * + * Note that this class depends on concrete implementations of the + * following classes to be provided via dependency injection: + * + * - VaultConfigurationService + * - VaultSecretService + * + * Bindings providing these concrete implementations will need to be + * provided by subclasses of VaultAuthenticationProviderModule for each + * supported vault. + * + * @param userContext + * The UserContext instance to decorate. + */ + @AssistedInject + public VaultUserContext(@Assisted UserContext userContext) { + super(userContext); + } + + /** + * Creates a new TokenFilter instance with token values set for all tokens + * which are not specific to connections or connection groups. Currently, + * this is only the username token ("GUAC_USERNAME"). + * + * @return + * A new TokenFilter instance with token values set for all tokens + * which are not specific to connections or connection groups. + */ + private TokenFilter createFilter() { + TokenFilter filter = new TokenFilter(); + filter.setToken(USERNAME_TOKEN, self().getIdentifier()); + return filter; + } + + /** + * Retrieve all applicable tokens and corresponding values from the vault, + * using the given TokenFilter to filter tokens within the secret names + * prior to retrieving those secrets. + * + * @param tokenMapping + * The mapping dictating the name of the secret which maps to each + * parameter token, where the key is the name of the parameter token + * and the value is the name of the secret. The name of the secret + * may contain its own tokens, which will be substituted using values + * from the given filter. + * + * @param filter + * The filter to use to substitute values for tokens in the names of + * secrets to be retrieved from the vault. + * + * @return + * The tokens which should be added to the in-progress call to + * connect(). + * + * @throws GuacamoleException + * If the value for any applicable secret cannot be retrieved from the + * vault due to an error. + */ + private Map getTokens(Map tokenMapping, + TokenFilter filter) throws GuacamoleException { + + Map tokens = new HashMap<>(); + + // Populate map with tokens containing the values of all secrets + // indicated in the token mapping + for (Map.Entry entry : tokenMapping.entrySet()) { + + // Translate secret pattern into secret name, ignoring any + // secrets which cannot be translated + String secretName; + try { + secretName = secretService.canonicalize(filter.filterStrict(entry.getValue())); + } + catch (GuacamoleTokenUndefinedException e) { + logger.debug("Secret for token \"{}\" will not be retrieved. " + + "Token \"{}\" within mapped secret name has no " + + "defined value in the current context.", + entry.getKey(), e.getTokenName()); + continue; + } + + // If a value is defined for the secret in question, store that + // value under the mapped token + String tokenName = entry.getKey(); + String secretValue = secretService.getValue(secretName); + if (secretValue != null) { + tokens.put(tokenName, secretValue); + logger.debug("Token \"{}\" populated with value from " + + "secret \"{}\".", tokenName, secretName); + } + else + logger.debug("Token \"{}\" not populated. Mapped " + + "secret \"{}\" has no value.", + tokenName, secretName); + + } + + return tokens; + + } + + @Override + protected Map getTokens(ConnectionGroup connectionGroup) + throws GuacamoleException { + + String name = connectionGroup.getName(); + String identifier = connectionGroup.getIdentifier(); + logger.debug("Injecting tokens from vault for connection group " + + "\"{}\" (\"{}\").", identifier, name); + + // Add general and connection-group-specific tokens + TokenFilter filter = createFilter(); + filter.setToken(CONNECTION_GROUP_NAME_TOKEN, name); + filter.setToken(CONNECTION_GROUP_IDENTIFIER_TOKEN, identifier); + + // Substitute tokens producing secret names, retrieving and storing + // those secrets as parameter tokens + return getTokens(confService.getTokenMapping(), filter); + + } + + @Override + protected Map getTokens(Connection connection) + throws GuacamoleException { + + String name = connection.getName(); + String identifier = connection.getIdentifier(); + logger.debug("Injecting tokens from vault for connection \"{}\" " + + "(\"{}\").", identifier, name); + + // Add general and connection-specific tokens + TokenFilter filter = createFilter(); + filter.setToken(CONNECTION_NAME_TOKEN, connection.getName()); + filter.setToken(CONNECTION_IDENTIFIER_TOKEN, identifier); + + // Add hostname and username tokens if available (implementations are + // not required to expose connection configuration details) + + GuacamoleConfiguration config = connection.getConfiguration(); + + String hostname = config.getParameter("hostname"); + if (hostname != null) + filter.setToken(CONNECTION_HOSTNAME_TOKEN, hostname); + else + logger.debug("Hostname for connection \"{}\" (\"{}\") not " + + "available. \"{}\" token will not be populated in " + + "secret names.", identifier, name, + CONNECTION_HOSTNAME_TOKEN); + + String username = config.getParameter("username"); + if (username != null) + filter.setToken(CONNECTION_USERNAME_TOKEN, username); + else + logger.debug("Username for connection \"{}\" (\"{}\") not " + + "available. \"{}\" token will not be populated in " + + "secret names.", identifier, name, + CONNECTION_USERNAME_TOKEN); + + // Substitute tokens producing secret names, retrieving and storing + // those secrets as parameter tokens + return getTokens(confService.getTokenMapping(), filter); + + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContextFactory.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContextFactory.java new file mode 100644 index 000000000..712d41a9b --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContextFactory.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.user; + +import org.apache.guacamole.net.auth.UserContext; + +/** + * Factory for creating UserContext instances which automatically inject tokens + * containing the values of secrets retrieved from a vault. + */ +public interface VaultUserContextFactory { + + /** + * Returns a new instance of a UserContext implementation which + * automatically injects tokens containing values of secrets retrieved from + * a vault. The given UserContext is decorated such that connections and + * connection groups will receive additional tokens during the connection + * process. + * + * @param userContext + * The UserContext instance to decorate. + * + * @return + * A new UserContext instance which automatically injects tokens + * containing values of secrets retrieved from a vault. + */ + UserContext create(UserContext userContext); + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/resources/translations/en.json b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/resources/translations/en.json new file mode 100644 index 000000000..c96ec2821 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/resources/translations/en.json @@ -0,0 +1,7 @@ +{ + + "DATA_SOURCE_AZURE_KEYVAULT" : { + "NAME" : "Azure Key Vault" + } + +} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/.ratignore b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/.ratignore new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/pom.xml b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/pom.xml new file mode 100644 index 000000000..b239f28bf --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/pom.xml @@ -0,0 +1,63 @@ + + + + + 4.0.0 + org.apache.guacamole + guacamole-auth-vault-dist + pom + guacamole-auth-vault-dist + http://guacamole.apache.org/ + + + UTF-8 + + + + org.apache.guacamole + guacamole-auth-vault + 1.4.0 + ../../ + + + + + + + org.apache.guacamole + guacamole-auth-vault-azure + 1.4.0 + + + + + + + + ${project.parent.artifactId}-${project.parent.version} + + + + diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/src/main/assembly/dist.xml b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/src/main/assembly/dist.xml new file mode 100644 index 000000000..b0ea3b1f6 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/src/main/assembly/dist.xml @@ -0,0 +1,54 @@ + + + + + dist + ${project.parent.artifactId}-${project.parent.version} + + + + tar.gz + + + + + + + + azure + + org.apache.guacamole:guacamole-auth-vault-azure + + + + + + + + + + target/licenses + + + + diff --git a/extensions/guacamole-auth-vault/pom.xml b/extensions/guacamole-auth-vault/pom.xml new file mode 100644 index 000000000..a924af8b3 --- /dev/null +++ b/extensions/guacamole-auth-vault/pom.xml @@ -0,0 +1,67 @@ + + + + + 4.0.0 + org.apache.guacamole + guacamole-auth-vault + pom + 1.4.0 + guacamole-auth-vault + http://guacamole.apache.org/ + + + org.apache.guacamole + extensions + 1.4.0 + ../ + + + + + + modules/guacamole-auth-vault-dist + + + modules/guacamole-auth-vault-base + + + modules/guacamole-auth-vault-azure + + + + + + + + + org.apache.guacamole + guacamole-ext + 1.4.0 + provided + + + + + + diff --git a/extensions/pom.xml b/extensions/pom.xml index 938e7cc62..18e307e54 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -48,6 +48,7 @@ guacamole-auth-quickconnect guacamole-auth-sso guacamole-auth-totp + guacamole-auth-vault diff --git a/pom.xml b/pom.xml index bcfa885ad..2b9b1641f 100644 --- a/pom.xml +++ b/pom.xml @@ -209,8 +209,8 @@ 1.8 -Xlint:all - -Werror + true true @@ -382,6 +382,11 @@ jackson-dataformat-yaml ${jackson.version} + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson.version} + com.fasterxml.jackson.module jackson-module-jaxb-annotations From cab29bacf73644449ad93fce88033c22c7b8b8d8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 04/36] GUACAMOLE-641: Automatically cache requests for secrets from the vault. --- .../AzureKeyVaultConfigurationService.java | 29 +++ .../secret/AzureKeyVaultSecretService.java | 23 +-- .../secret/CachedVaultSecretService.java | 192 ++++++++++++++++++ 3 files changed, 231 insertions(+), 13 deletions(-) create mode 100644 extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java index 2be7bd1b0..e8e1ceb51 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java @@ -25,6 +25,7 @@ import com.microsoft.aad.adal4j.ClientCredential; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.vault.conf.VaultConfigurationService; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; /** @@ -46,6 +47,19 @@ public class AzureKeyVaultConfigurationService extends VaultConfigurationService */ private static final String TOKEN_MAPPING_FILENAME = "azure-keyvault-token-mapping.json"; + /** + * The number of milliseconds that each retrieved secret should be cached + * for. + */ + private static final IntegerGuacamoleProperty SECRET_TTL = new IntegerGuacamoleProperty() { + + @Override + public String getName() { + return "azure-keyvault-secret-ttl"; + } + + }; + /** * The URL of the Azure Key Vault that should be used to populate token * values. @@ -95,6 +109,21 @@ public class AzureKeyVaultConfigurationService extends VaultConfigurationService super(TOKEN_MAPPING_FILENAME); } + /** + * Returns the number of milliseconds that each retrieved secret should be + * cached for. By default, secrets are cached for 10 seconds. + * + * @return + * The number of milliseconds to cache each retrieved secret. + * + * @throws GuacamoleException + * If the value specified within guacamole.properties cannot be + * parsed. + */ + public int getSecretTTL() throws GuacamoleException { + return environment.getProperty(SECRET_TTL, 10000); + } + /** * Returns the base URL of the Azure Key Vault containing the secrets that * should be retrieved to populate connection parameter tokens. The base diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java index ccbd6c9cc..aa46b7ac0 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java @@ -31,13 +31,13 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultAuthenticationException; import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultConfigurationService; -import org.apache.guacamole.auth.vault.secret.VaultSecretService; +import org.apache.guacamole.auth.vault.secret.CachedVaultSecretService; /** * Service which retrieves secrets from Azure Key Vault. */ @Singleton -public class AzureKeyVaultSecretService implements VaultSecretService { +public class AzureKeyVaultSecretService extends CachedVaultSecretService { /** * Pattern which matches contiguous groups of characters which are not @@ -71,23 +71,20 @@ public class AzureKeyVaultSecretService implements VaultSecretService { } @Override - public String getValue(String name) throws GuacamoleException { + protected CachedSecret refreshCachedSecret(String name) + throws GuacamoleException { + + int ttl = confService.getSecretTTL(); + String url = confService.getVaultURL(); try { - // Retrieve configuration information necessary for connecting to - // Azure Key Vault - String url = confService.getVaultURL(); - KeyVaultCredentials credentials = credentialProvider.get(); - - // Authenticate against Azure Key Vault - KeyVaultClient client = new KeyVaultClient(credentials); - - // Retrieve requested secret + // Retrieve requested secret from Azure Key Vault + KeyVaultClient client = new KeyVaultClient(credentialProvider.get()); SecretBundle secret = client.getSecret(url, name); // FIXME: STUB - return null; + return new CachedSecret(null, ttl); } catch (AzureKeyVaultAuthenticationException e) { diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java new file mode 100644 index 000000000..838449857 --- /dev/null +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.vault.secret; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Caching implementation of VaultSecretService. Requests for the values of + * secrets will automatically be cached for a duration determined by the + * implementation. Subclasses must implement refreshCachedSecret() to provide + * a mechanism for CachedVaultSecretService to explicitly retrieve a value + * which is missing from the cache or has expired. + */ +public abstract class CachedVaultSecretService implements VaultSecretService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(CachedVaultSecretService.class); + + /** + * The cached value of a secret. + */ + protected class CachedSecret { + + /** + * The value of the secret at the time it was last retrieved. + */ + private final String value; + + /** + * The time the value should be considered out-of-date, in milliseconds + * since midnight of January 1, 1970 UTC. + */ + private final long expires; + + /** + * Creates a new CachedSecret which represents a cached snapshot of the + * value of a secret. Each CachedSecret has a limited lifespan after + * which it should be considered out-of-date. + * + * @param value + * The current value of the secret. + * + * @param ttl + * The maximum number of milliseconds that this value should be + * cached. + */ + public CachedSecret(String value, int ttl) { + this.value = value; + this.expires = System.currentTimeMillis() + ttl; + } + + /** + * Returns the value of the secret at the time it was last retrieved. + * The actual value of the secret may have changed. + * + * @return + * The value of the secret at the time it was last retrieved. + */ + public String getValue() { + return value; + } + + /** + * Returns whether this specific cached value has expired. Expired + * values will be automatically refreshed by CachedVaultSecretService. + * + * @return + * true if this cached value has expired, false otherwise. + */ + public boolean isExpired() { + return System.currentTimeMillis() >= expires; + } + + } + + /** + * Cache of past requests to retrieve secrets. Expired secrets are lazily + * removed. + */ + private final ConcurrentHashMap> cache = new ConcurrentHashMap<>(); + + /** + * Explicitly retrieves the value of the secret having the given name, + * returning a result that can be cached. The length of time that this + * specific value will be cached is determined by the TTL value provided to + * the returned CachedSecret. This function will be automatically invoked + * in response to calls to getValue() when the requested secret is either + * not cached or has expired. Expired secrets are not removed from the + * cache until another request is made for that secret. + * + * @param name + * The name of the secret to retrieve. + * + * @return + * A CachedSecret which defines the current value of the secret and the + * point in time that value should be considered potentially + * out-of-date. + * + * @throws GuacamoleException + * If an error occurs while retrieving the secret from the vault. + */ + protected abstract CachedSecret refreshCachedSecret(String name) + throws GuacamoleException; + + @Override + public String getValue(String name) throws GuacamoleException { + + CompletableFuture refreshEntry; + + try { + + // Attempt to use cached result of previous call + Future cachedEntry = cache.get(name); + if (cachedEntry != null) { + + // Use cached result if not yet expired + CachedSecret secret = cachedEntry.get(); + if (!secret.isExpired()) { + logger.debug("Using cached secret for \"{}\".", name); + return secret.getValue(); + } + + // Evict if expired + else { + logger.debug("Cached secret for \"{}\" is expired.", name); + cache.remove(name, cachedEntry); + } + + } + + // If no cached result, or result is too old, race with other + // threads to be the thread which refreshes the entry + refreshEntry = new CompletableFuture<>(); + cachedEntry = cache.putIfAbsent(name, refreshEntry); + + // If a refresh operation is already in progress, wait for that + // operation to complete and use its value + if (cachedEntry != null) + return cachedEntry.get().getValue(); + + } + catch (InterruptedException | ExecutionException e) { + throw new GuacamoleServerException("Attempt to retrieve secret " + + "failed.", e); + } + + // If we reach this far, the cache entry is stale or missing, and it's + // this thread's responsibility to refresh the entry + try { + CachedSecret secret = refreshCachedSecret(name); + refreshEntry.complete(secret); + logger.debug("Cached secret for \"{}\" has been refreshed.", name); + return secret.getValue(); + } + + // Abort the refresh operation if an error occurs + catch (Error | RuntimeException | GuacamoleException e) { + refreshEntry.completeExceptionally(e); + cache.remove(name, refreshEntry); + logger.debug("Cached secret for \"{}\" could not be refreshed.", name); + throw e; + } + + } + +} From e56becc258a87337841b2c47846a95c57d470f2b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 05/36] GUACAMOLE-641: Retrieve secrets from Azure Key Vault. --- .../auth/vault/azure/secret/AzureKeyVaultSecretService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java index aa46b7ac0..8498f81ea 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java @@ -83,8 +83,9 @@ public class AzureKeyVaultSecretService extends CachedVaultSecretService { KeyVaultClient client = new KeyVaultClient(credentialProvider.get()); SecretBundle secret = client.getSecret(url, name); - // FIXME: STUB - return new CachedSecret(null, ttl); + // Cache retrieved value + String value = (secret != null) ? secret.value() : null; + return new CachedSecret(value, ttl); } catch (AzureKeyVaultAuthenticationException e) { From 2f946d962be9aa6d72df2b24cdb5327833b7b187 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 06/36] GUACAMOLE-641: Allow tokens to be easily injected on-demand. --- .../net/auth/TokenInjectingConnection.java | 32 +++++++++++++++++- .../auth/TokenInjectingConnectionGroup.java | 33 ++++++++++++++++++- .../net/auth/TokenInjectingUserContext.java | 18 ++++++++-- 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java index 8a826d88c..51ca6e33e 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java @@ -19,6 +19,7 @@ package org.apache.guacamole.net.auth; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.guacamole.GuacamoleException; @@ -37,6 +38,22 @@ public class TokenInjectingConnection extends DelegatingConnection { */ private final Map tokens; + /** + * Returns the tokens which should be added to an in-progress call to + * connect(). If not overridden, this function will return the tokens + * provided when this instance of TokenInjectingConnection was created. + * + * @return + * The tokens which should be added to the in-progress call to + * connect(). + * + * @throws GuacamoleException + * If the applicable tokens cannot be generated. + */ + protected Map getTokens() throws GuacamoleException { + return tokens; + } + /** * Wraps the given Connection, automatically adding the given tokens to * each invocation of connect(). Any additional tokens which have the same @@ -54,13 +71,26 @@ public class TokenInjectingConnection extends DelegatingConnection { this.tokens = tokens; } + /** + * Wraps the given Connection such that the additional parameter tokens + * returned by getTokens() are included with each invocation of connect(). + * Any additional tokens which have the same name as existing tokens will + * override the existing values. + * + * @param connection + * The Connection to wrap. + */ + public TokenInjectingConnection(Connection connection) { + this(connection, Collections.emptyMap()); + } + @Override public GuacamoleTunnel connect(GuacamoleClientInformation info, Map tokens) throws GuacamoleException { // Apply provided tokens over those given to connect() tokens = new HashMap<>(tokens); - tokens.putAll(this.tokens); + tokens.putAll(getTokens()); return super.connect(info, tokens); diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java index 0ec93baf4..62f71630a 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java @@ -19,6 +19,7 @@ package org.apache.guacamole.net.auth; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.guacamole.GuacamoleException; @@ -37,6 +38,23 @@ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup { */ private final Map tokens; + /** + * Returns the tokens which should be added to an in-progress call to + * connect(). If not overridden, this function will return the tokens + * provided when this instance of TokenInjectingConnectionGroup was + * created. + * + * @return + * The tokens which should be added to the in-progress call to + * connect(). + * + * @throws GuacamoleException + * If the applicable tokens cannot be generated. + */ + protected Map getTokens() throws GuacamoleException { + return tokens; + } + /** * Wraps the given ConnectionGroup, automatically adding the given tokens * to each invocation of connect(). Any additional tokens which have the @@ -54,13 +72,26 @@ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup { this.tokens = tokens; } + /** + * Wraps the given ConnectionGroup such that the additional parameter + * tokens returned by getTokens() are included with each invocation of + * connect(). Any additional tokens which have the same name as existing + * tokens will override the existing values. + * + * @param connectionGroup + * The ConnectionGroup to wrap. + */ + public TokenInjectingConnectionGroup(ConnectionGroup connectionGroup) { + this(connectionGroup, Collections.emptyMap()); + } + @Override public GuacamoleTunnel connect(GuacamoleClientInformation info, Map tokens) throws GuacamoleException { // Apply provided tokens over those given to connect() tokens = new HashMap<>(tokens); - tokens.putAll(this.tokens); + tokens.putAll(getTokens()); return super.connect(info, tokens); diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java index 1f6d3d60e..e4940e5d8 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java @@ -122,7 +122,14 @@ public class TokenInjectingUserContext extends DelegatingUserContext { @Override protected ConnectionGroup decorate(ConnectionGroup object) throws GuacamoleException { - return new TokenInjectingConnectionGroup(object, getTokens(object)); + return new TokenInjectingConnectionGroup(object) { + + @Override + protected Map getTokens() throws GuacamoleException { + return TokenInjectingUserContext.this.getTokens(object); + } + + }; } @Override @@ -140,7 +147,14 @@ public class TokenInjectingUserContext extends DelegatingUserContext { @Override protected Connection decorate(Connection object) throws GuacamoleException { - return new TokenInjectingConnection(object, getTokens(object)); + return new TokenInjectingConnection(object) { + + @Override + protected Map getTokens() throws GuacamoleException { + return TokenInjectingUserContext.this.getTokens(object); + } + + }; } @Override From 3dbb821baf9c29078ccf49290c4d15e2262da6fa Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 07/36] GUACAMOLE-641: Retrieve tokens asynchronously and in parallel. --- .../secret/AzureKeyVaultSecretService.java | 48 +++++++++--- .../secret/CachedVaultSecretService.java | 24 ++++-- .../auth/vault/secret/VaultSecretService.java | 15 ++-- .../auth/vault/user/VaultUserContext.java | 76 +++++++++++++++---- 4 files changed, 122 insertions(+), 41 deletions(-) diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java index 8498f81ea..65d75dc9b 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java @@ -25,10 +25,11 @@ import com.google.inject.Singleton; import com.microsoft.azure.keyvault.KeyVaultClient; import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; import com.microsoft.azure.keyvault.models.SecretBundle; +import com.microsoft.rest.ServiceCallback; +import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultAuthenticationException; import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultConfigurationService; import org.apache.guacamole.auth.vault.secret.CachedVaultSecretService; @@ -77,20 +78,43 @@ public class AzureKeyVaultSecretService extends CachedVaultSecretService { int ttl = confService.getSecretTTL(); String url = confService.getVaultURL(); - try { + CompletableFuture retrievedValue = new CompletableFuture<>(); - // Retrieve requested secret from Azure Key Vault - KeyVaultClient client = new KeyVaultClient(credentialProvider.get()); - SecretBundle secret = client.getSecret(url, name); + // getSecretAsync() still blocks for around half a second, despite + // technically being asynchronous + (new Thread() { - // Cache retrieved value - String value = (secret != null) ? secret.value() : null; - return new CachedSecret(value, ttl); + @Override + public void run() { + try { - } - catch (AzureKeyVaultAuthenticationException e) { - throw new GuacamoleServerException("Unable to authenticate with Azure.", e); - } + // Retrieve requested secret from Azure Key Vault + KeyVaultClient client = new KeyVaultClient(credentialProvider.get()); + client.getSecretAsync(url, name, new ServiceCallback() { + + @Override + public void failure(Throwable t) { + retrievedValue.completeExceptionally(t); + } + + @Override + public void success(SecretBundle secret) { + String value = (secret != null) ? secret.value() : null; + retrievedValue.complete(value); + } + + }); + + } + catch (AzureKeyVaultAuthenticationException e) { + retrievedValue.completeExceptionally(e); + } + } + + }).start(); + + // Cache retrieved value + return new CachedSecret(retrievedValue, ttl); } diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java index 838449857..63e95ce6d 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java @@ -48,9 +48,10 @@ public abstract class CachedVaultSecretService implements VaultSecretService { protected class CachedSecret { /** - * The value of the secret at the time it was last retrieved. + * A Future which contains or will contain the value of the secret at + * the time it was last retrieved. */ - private final String value; + private final Future value; /** * The time the value should be considered out-of-date, in milliseconds @@ -64,13 +65,15 @@ public abstract class CachedVaultSecretService implements VaultSecretService { * which it should be considered out-of-date. * * @param value - * The current value of the secret. + * A Future which contains or will contain the current value of the + * secret. If no such secret exists, the given Future should + * complete with null. * * @param ttl * The maximum number of milliseconds that this value should be * cached. */ - public CachedSecret(String value, int ttl) { + public CachedSecret(Future value, int ttl) { this.value = value; this.expires = System.currentTimeMillis() + ttl; } @@ -80,9 +83,14 @@ public abstract class CachedVaultSecretService implements VaultSecretService { * The actual value of the secret may have changed. * * @return - * The value of the secret at the time it was last retrieved. + * A Future which will eventually complete with the value of the + * secret at the time it was last retrieved. If no such secret + * exists, the Future will be completed with null. If an error + * occurs which prevents retrieval of the secret, that error will + * be exposed through an ExecutionException when an attempt is made + * to retrieve the value from the Future. */ - public String getValue() { + public Future getValue() { return value; } @@ -129,7 +137,7 @@ public abstract class CachedVaultSecretService implements VaultSecretService { throws GuacamoleException; @Override - public String getValue(String name) throws GuacamoleException { + public Future getValue(String name) throws GuacamoleException { CompletableFuture refreshEntry; @@ -175,7 +183,7 @@ public abstract class CachedVaultSecretService implements VaultSecretService { try { CachedSecret secret = refreshCachedSecret(name); refreshEntry.complete(secret); - logger.debug("Cached secret for \"{}\" has been refreshed.", name); + logger.debug("Cached secret for \"{}\" will be refreshed.", name); return secret.getValue(); } diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java index 49d0d9ce3..a78826eb5 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java @@ -19,6 +19,7 @@ package org.apache.guacamole.auth.vault.secret; +import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; /** @@ -49,19 +50,23 @@ public interface VaultSecretService { String canonicalize(String name); /** - * Returns the value of the secret having the given name. If no such - * secret exists, null is returned. + * Returns a Future which eventually completes with the value of the secret + * having the given name. If no such secret exists, the Future will be + * completed with null. * * @param name * The name of the secret to retrieve. * * @return - * The value of the secret having the given name, or null if no such - * secret exists. + * A Future which completes with value of the secret having the given + * name. If no such secret exists, the Future will be completed with + * null. If an error occurs asynchronously which prevents retrieval of + * the secret, that error will be exposed through an ExecutionException + * when an attempt is made to retrieve the value from the Future. * * @throws GuacamoleException * If the secret cannot be retrieved due to an error. */ - String getValue(String name) throws GuacamoleException; + Future getValue(String name) throws GuacamoleException; } diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java index 13cb6d515..5e8e6276d 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java @@ -24,7 +24,10 @@ import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.auth.vault.conf.VaultConfigurationService; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; @@ -149,9 +152,9 @@ public class VaultUserContext extends TokenInjectingUserContext { } /** - * Retrieve all applicable tokens and corresponding values from the vault, - * using the given TokenFilter to filter tokens within the secret names - * prior to retrieving those secrets. + * Initiates asynchronous retrieval of all applicable tokens and + * corresponding values from the vault, using the given TokenFilter to + * filter tokens within the secret names prior to retrieving those secrets. * * @param tokenMapping * The mapping dictating the name of the secret which maps to each @@ -165,20 +168,20 @@ public class VaultUserContext extends TokenInjectingUserContext { * secrets to be retrieved from the vault. * * @return - * The tokens which should be added to the in-progress call to - * connect(). + * A Map of token name to Future, where each Future represents the + * pending retrieval operation which will ultimately be completed with + * the value of all secrets mapped to that token. * * @throws GuacamoleException * If the value for any applicable secret cannot be retrieved from the * vault due to an error. */ - private Map getTokens(Map tokenMapping, + private Map> getTokens(Map tokenMapping, TokenFilter filter) throws GuacamoleException { - Map tokens = new HashMap<>(); - - // Populate map with tokens containing the values of all secrets - // indicated in the token mapping + // Populate map with pending secret retrieval operations corresponding + // to each mapped token + Map> pendingTokens = new HashMap<>(tokenMapping.size()); for (Map.Entry entry : tokenMapping.entrySet()) { // Translate secret pattern into secret name, ignoring any @@ -195,19 +198,60 @@ public class VaultUserContext extends TokenInjectingUserContext { continue; } + // Initiate asynchronous retrieval of the token value + String tokenName = entry.getKey(); + Future secret = secretService.getValue(secretName); + pendingTokens.put(tokenName, secret); + + } + + return pendingTokens; + + } + + /** + * Waits for all pending secret retrieval operations to complete, + * transforming each Future within the given Map into its contained String + * value. + * + * @param pendingTokens + * A Map of token name to Future, where each Future represents the + * pending retrieval operation which will ultimately be completed with + * the value of all secrets mapped to that token. + * + * @throws GuacamoleException + * If the value for any applicable secret cannot be retrieved from the + * vault due to an error. + */ + private Map resolve(Map> pendingTokens) throws GuacamoleException { + + // Populate map with tokens containing the values of their + // corresponding secrets + Map tokens = new HashMap<>(pendingTokens.size()); + for (Map.Entry> entry : pendingTokens.entrySet()) { + + // Complete secret retrieval operation, blocking if necessary + String secretValue; + try { + secretValue = entry.getValue().get(); + } + catch (InterruptedException | ExecutionException e) { + throw new GuacamoleServerException("Retrieval of secret value " + + "failed.", e); + } + // If a value is defined for the secret in question, store that // value under the mapped token String tokenName = entry.getKey(); - String secretValue = secretService.getValue(secretName); if (secretValue != null) { tokens.put(tokenName, secretValue); logger.debug("Token \"{}\" populated with value from " - + "secret \"{}\".", tokenName, secretName); + + "secret.", tokenName); } else logger.debug("Token \"{}\" not populated. Mapped " - + "secret \"{}\" has no value.", - tokenName, secretName); + + "secret has no value.", tokenName); } @@ -231,7 +275,7 @@ public class VaultUserContext extends TokenInjectingUserContext { // Substitute tokens producing secret names, retrieving and storing // those secrets as parameter tokens - return getTokens(confService.getTokenMapping(), filter); + return resolve(getTokens(confService.getTokenMapping(), filter)); } @@ -274,7 +318,7 @@ public class VaultUserContext extends TokenInjectingUserContext { // Substitute tokens producing secret names, retrieving and storing // those secrets as parameter tokens - return getTokens(confService.getTokenMapping(), filter); + return resolve(getTokens(confService.getTokenMapping(), filter)); } From e0fce54056487fcb4f3c83426ff9cc478801afc1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 08/36] GUACAMOLE-641: Correct typo in documentation - "AzureKeyVaultAuthenticationiProviderModule" should be "AzureKeyVaultAuthenticationProviderModule". --- .../vault/azure/AzureKeyVaultAuthenticationProviderModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java index 3cba8b705..ed8474abe 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java @@ -36,7 +36,7 @@ public class AzureKeyVaultAuthenticationProviderModule extends VaultAuthenticationProviderModule { /** - * Creates a new AzureKeyVaultAuthenticationiProviderModule which + * Creates a new AzureKeyVaultAuthenticationProviderModule which * configures dependency injection for the Azure Key Vault authentication * provider and related services. * From 0359aa6225f929d365a8e649a438d88582512b42 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:40 -0800 Subject: [PATCH 09/36] GUACAMOLE-641: Follow widely-accepted `public static final Logger` idiom. From https://github.com/apache/guacamole-client/pull/336#discussion_r241549475: > > SLF4J formerly recommended that instance variables be used > (non-static), but no longer takes either stance: > https://www.slf4j.org/faq.html#declared_static > > If we have to pick something to be the standard going forward, I'd > say let's stick with the accepted idiom of `private static final` > loggers, with the exception being where it's actually necessary to > not be `static` (dependency injection). > --- .../guacamole/auth/vault/secret/CachedVaultSecretService.java | 2 +- .../org/apache/guacamole/auth/vault/user/VaultUserContext.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java index 63e95ce6d..e3f26bdd3 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java @@ -40,7 +40,7 @@ public abstract class CachedVaultSecretService implements VaultSecretService { /** * Logger for this class. */ - private final Logger logger = LoggerFactory.getLogger(CachedVaultSecretService.class); + private static final Logger logger = LoggerFactory.getLogger(CachedVaultSecretService.class); /** * The cached value of a secret. diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java index 5e8e6276d..9986dc32a 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java @@ -49,7 +49,7 @@ public class VaultUserContext extends TokenInjectingUserContext { /** * Logger for this class. */ - private final Logger logger = LoggerFactory.getLogger(VaultUserContext.class); + private static final Logger logger = LoggerFactory.getLogger(VaultUserContext.class); /** * The name of the token which will be replaced with the username of the From 2df24bf91124f476fdd66f5693a9502032a50ac3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 10/36] GUACAMOLE-641: Document return type of VaultUserContext.resolve(). --- .../apache/guacamole/auth/vault/user/VaultUserContext.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java index 9986dc32a..0a03b78a0 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java +++ b/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java @@ -219,6 +219,10 @@ public class VaultUserContext extends TokenInjectingUserContext { * pending retrieval operation which will ultimately be completed with * the value of all secrets mapped to that token. * + * @return + * A Map of token name to the corresponding String value retrieved for + * that token from the vault. + * * @throws GuacamoleException * If the value for any applicable secret cannot be retrieved from the * vault due to an error. From f99b3a32138f61cb3bffcd88dd2f7b0aea46b364 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 11/36] GUACAMOLE-641: Rename guacamole-auth-vault to guacamole-vault (does not provide auth). --- .../.ratignore | 0 .../modules/guacamole-vault-azure}/.ratignore | 0 .../modules/guacamole-vault-azure}/pom.xml | 8 ++++---- .../azure/AzureKeyVaultAuthenticationProvider.java | 4 ++-- .../AzureKeyVaultAuthenticationProviderModule.java | 14 +++++++------- .../conf/AzureKeyVaultAuthenticationException.java | 2 +- .../conf/AzureKeyVaultConfigurationService.java | 4 ++-- .../vault/azure/conf/AzureKeyVaultCredentials.java | 2 +- .../azure/secret/AzureKeyVaultSecretService.java | 8 ++++---- .../src/main/resources/guac-manifest.json | 2 +- .../modules/guacamole-vault-base}/.ratignore | 0 .../modules/guacamole-vault-base}/pom.xml | 6 +++--- .../vault/VaultAuthenticationProvider.java | 4 ++-- .../vault/VaultAuthenticationProviderModule.java | 6 +++--- .../vault/conf/VaultConfigurationService.java | 4 ++-- .../vault/secret/CachedVaultSecretService.java | 2 +- .../vault/secret/VaultSecretService.java | 2 +- .../guacamole}/vault/user/VaultUserContext.java | 6 +++--- .../vault/user/VaultUserContextFactory.java | 2 +- .../src/main/resources/translations/en.json | 0 .../modules/guacamole-vault-dist}/.ratignore | 0 .../modules/guacamole-vault-dist}/pom.xml | 13 ++++++------- .../src/main/assembly/dist.xml | 2 +- .../pom.xml | 10 +++++----- extensions/pom.xml | 4 +++- 25 files changed, 53 insertions(+), 52 deletions(-) rename extensions/{guacamole-auth-vault => guacamole-vault}/.ratignore (100%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure => guacamole-vault/modules/guacamole-vault-azure}/.ratignore (100%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure => guacamole-vault/modules/guacamole-vault-azure}/pom.xml (97%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole}/vault/azure/AzureKeyVaultAuthenticationProvider.java (93%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole}/vault/azure/AzureKeyVaultAuthenticationProviderModule.java (80%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole}/vault/azure/conf/AzureKeyVaultAuthenticationException.java (97%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole}/vault/azure/conf/AzureKeyVaultConfigurationService.java (97%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole}/vault/azure/conf/AzureKeyVaultCredentials.java (98%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole}/vault/azure/secret/AzureKeyVaultSecretService.java (92%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-azure => guacamole-vault/modules/guacamole-vault-azure}/src/main/resources/guac-manifest.json (71%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base => guacamole-vault/modules/guacamole-vault-base}/.ratignore (100%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base => guacamole-vault/modules/guacamole-vault-base}/pom.xml (93%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole}/vault/VaultAuthenticationProvider.java (95%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole}/vault/VaultAuthenticationProviderModule.java (95%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole}/vault/conf/VaultConfigurationService.java (96%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole}/vault/secret/CachedVaultSecretService.java (99%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole}/vault/secret/VaultSecretService.java (98%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole}/vault/user/VaultUserContext.java (98%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth => guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole}/vault/user/VaultUserContextFactory.java (97%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-base => guacamole-vault/modules/guacamole-vault-base}/src/main/resources/translations/en.json (100%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-dist => guacamole-vault/modules/guacamole-vault-dist}/.ratignore (100%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-dist => guacamole-vault/modules/guacamole-vault-dist}/pom.xml (82%) rename extensions/{guacamole-auth-vault/modules/guacamole-auth-vault-dist => guacamole-vault/modules/guacamole-vault-dist}/src/main/assembly/dist.xml (95%) rename extensions/{guacamole-auth-vault => guacamole-vault}/pom.xml (88%) diff --git a/extensions/guacamole-auth-vault/.ratignore b/extensions/guacamole-vault/.ratignore similarity index 100% rename from extensions/guacamole-auth-vault/.ratignore rename to extensions/guacamole-vault/.ratignore diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/.ratignore b/extensions/guacamole-vault/modules/guacamole-vault-azure/.ratignore similarity index 100% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/.ratignore rename to extensions/guacamole-vault/modules/guacamole-vault-azure/.ratignore diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/pom.xml b/extensions/guacamole-vault/modules/guacamole-vault-azure/pom.xml similarity index 97% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/pom.xml rename to extensions/guacamole-vault/modules/guacamole-vault-azure/pom.xml index d1d336aa1..f2448a3d6 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/pom.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/pom.xml @@ -24,10 +24,10 @@ 4.0.0 org.apache.guacamole - guacamole-auth-vault-azure + guacamole-vault-azure jar 1.4.0 - guacamole-auth-vault-azure + guacamole-vault-azure http://guacamole.apache.org/ @@ -37,7 +37,7 @@ org.apache.guacamole - guacamole-auth-vault + guacamole-vault 1.4.0 ../../ @@ -70,7 +70,7 @@ org.apache.guacamole - guacamole-auth-vault-base + guacamole-vault-base 1.4.0 diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProvider.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProvider.java similarity index 93% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProvider.java rename to extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProvider.java index 5fd091374..f5f367e09 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProvider.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProvider.java @@ -17,10 +17,10 @@ * under the License. */ -package org.apache.guacamole.auth.vault.azure; +package org.apache.guacamole.vault.azure; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.vault.VaultAuthenticationProvider; +import org.apache.guacamole.vault.VaultAuthenticationProvider; /** * VaultAuthenticationProvider implementation which reads secrets from Azure diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProviderModule.java similarity index 80% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java rename to extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProviderModule.java index ed8474abe..a044743ab 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/AzureKeyVaultAuthenticationProviderModule.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProviderModule.java @@ -17,16 +17,16 @@ * under the License. */ -package org.apache.guacamole.auth.vault.azure; +package org.apache.guacamole.vault.azure; import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.vault.VaultAuthenticationProviderModule; -import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultConfigurationService; -import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultCredentials; -import org.apache.guacamole.auth.vault.azure.secret.AzureKeyVaultSecretService; -import org.apache.guacamole.auth.vault.conf.VaultConfigurationService; -import org.apache.guacamole.auth.vault.secret.VaultSecretService; +import org.apache.guacamole.vault.VaultAuthenticationProviderModule; +import org.apache.guacamole.vault.azure.conf.AzureKeyVaultConfigurationService; +import org.apache.guacamole.vault.azure.conf.AzureKeyVaultCredentials; +import org.apache.guacamole.vault.azure.secret.AzureKeyVaultSecretService; +import org.apache.guacamole.vault.conf.VaultConfigurationService; +import org.apache.guacamole.vault.secret.VaultSecretService; /** * Guice module which configures injections specific to Azure Key Vault diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultAuthenticationException.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultAuthenticationException.java similarity index 97% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultAuthenticationException.java rename to extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultAuthenticationException.java index 5cf92a1b6..d1eba9be7 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultAuthenticationException.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultAuthenticationException.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.vault.azure.conf; +package org.apache.guacamole.vault.azure.conf; /** * Unchecked exception thrown by AzureKeyVaultCredentials if an error occurs diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java similarity index 97% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java rename to extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java index e8e1ceb51..7bfb52919 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java @@ -17,16 +17,16 @@ * under the License. */ -package org.apache.guacamole.auth.vault.azure.conf; +package org.apache.guacamole.vault.azure.conf; import com.google.inject.Inject; import com.google.inject.Singleton; import com.microsoft.aad.adal4j.ClientCredential; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.vault.conf.VaultConfigurationService; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; +import org.apache.guacamole.vault.conf.VaultConfigurationService; /** * Service for retrieving configuration information regarding the Azure Key diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultCredentials.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultCredentials.java similarity index 98% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultCredentials.java rename to extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultCredentials.java index c69da5f1e..36bde5d53 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/conf/AzureKeyVaultCredentials.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultCredentials.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.vault.azure.conf; +package org.apache.guacamole.vault.azure.conf; import com.google.inject.Inject; import com.microsoft.aad.adal4j.AuthenticationContext; diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java similarity index 92% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java rename to extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java index 65d75dc9b..cd5ed3537 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/java/org/apache/guacamole/auth/vault/azure/secret/AzureKeyVaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.vault.azure.secret; +package org.apache.guacamole.vault.azure.secret; import com.google.inject.Inject; import com.google.inject.Provider; @@ -30,9 +30,9 @@ import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultAuthenticationException; -import org.apache.guacamole.auth.vault.azure.conf.AzureKeyVaultConfigurationService; -import org.apache.guacamole.auth.vault.secret.CachedVaultSecretService; +import org.apache.guacamole.vault.azure.conf.AzureKeyVaultAuthenticationException; +import org.apache.guacamole.vault.azure.conf.AzureKeyVaultConfigurationService; +import org.apache.guacamole.vault.secret.CachedVaultSecretService; /** * Service which retrieves secrets from Azure Key Vault. diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/resources/guac-manifest.json b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/resources/guac-manifest.json similarity index 71% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/resources/guac-manifest.json rename to extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/resources/guac-manifest.json index 87e1b1165..9f6ee94a4 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-azure/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/resources/guac-manifest.json @@ -6,7 +6,7 @@ "namespace" : "azure-keyvault", "authProviders" : [ - "org.apache.guacamole.auth.vault.azure.AzureKeyVaultAuthenticationProvider" + "org.apache.guacamole.vault.azure.AzureKeyVaultAuthenticationProvider" ], "translations" : [ diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/.ratignore b/extensions/guacamole-vault/modules/guacamole-vault-base/.ratignore similarity index 100% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/.ratignore rename to extensions/guacamole-vault/modules/guacamole-vault-base/.ratignore diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/pom.xml b/extensions/guacamole-vault/modules/guacamole-vault-base/pom.xml similarity index 93% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/pom.xml rename to extensions/guacamole-vault/modules/guacamole-vault-base/pom.xml index d59754fe6..87c6c9404 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/pom.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/pom.xml @@ -24,9 +24,9 @@ 4.0.0 org.apache.guacamole - guacamole-auth-vault-base + guacamole-vault-base jar - guacamole-auth-vault-base + guacamole-vault-base http://guacamole.apache.org/ @@ -35,7 +35,7 @@ org.apache.guacamole - guacamole-auth-vault + guacamole-vault 1.4.0 ../../ diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProvider.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProvider.java similarity index 95% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProvider.java rename to extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProvider.java index 0b9126a67..a18b9c05a 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProvider.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProvider.java @@ -17,16 +17,16 @@ * under the License. */ -package org.apache.guacamole.auth.vault; +package org.apache.guacamole.vault; import com.google.inject.Guice; import com.google.inject.Injector; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.vault.user.VaultUserContextFactory; import org.apache.guacamole.net.auth.AbstractAuthenticationProvider; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.vault.user.VaultUserContextFactory; /** * AuthenticationProvider implementation which automatically injects tokens diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProviderModule.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProviderModule.java similarity index 95% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProviderModule.java rename to extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProviderModule.java index 9e5ae7155..94119a7a2 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/VaultAuthenticationProviderModule.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProviderModule.java @@ -17,16 +17,16 @@ * under the License. */ -package org.apache.guacamole.auth.vault; +package org.apache.guacamole.vault; import com.google.inject.AbstractModule; import com.google.inject.assistedinject.FactoryModuleBuilder; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.vault.user.VaultUserContext; -import org.apache.guacamole.auth.vault.user.VaultUserContextFactory; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.vault.user.VaultUserContext; +import org.apache.guacamole.vault.user.VaultUserContextFactory; /** * Guice module which configures injections specific to the base support for diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/conf/VaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java similarity index 96% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/conf/VaultConfigurationService.java rename to extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java index 9cafbd45f..9c01f7030 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/conf/VaultConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.vault.conf; +package org.apache.guacamole.vault.conf; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -27,8 +27,8 @@ import java.io.IOException; import java.util.Map; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.auth.vault.VaultAuthenticationProviderModule; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.vault.VaultAuthenticationProviderModule; /** * Base class for services which retrieve key vault configuration information. diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/CachedVaultSecretService.java similarity index 99% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java rename to extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/CachedVaultSecretService.java index e3f26bdd3..b26fdba94 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/CachedVaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/CachedVaultSecretService.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.vault.secret; +package org.apache.guacamole.vault.secret; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java similarity index 98% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java rename to extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java index a78826eb5..04f3783df 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/secret/VaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.vault.secret; +package org.apache.guacamole.vault.secret; import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java similarity index 98% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java rename to extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java index 0a03b78a0..1adf5c54d 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContext.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.vault.user; +package org.apache.guacamole.vault.user; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; @@ -28,15 +28,15 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.auth.vault.conf.VaultConfigurationService; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.TokenInjectingUserContext; import org.apache.guacamole.net.auth.UserContext; -import org.apache.guacamole.auth.vault.secret.VaultSecretService; import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.apache.guacamole.token.GuacamoleTokenUndefinedException; import org.apache.guacamole.token.TokenFilter; +import org.apache.guacamole.vault.conf.VaultConfigurationService; +import org.apache.guacamole.vault.secret.VaultSecretService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContextFactory.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContextFactory.java similarity index 97% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContextFactory.java rename to extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContextFactory.java index 712d41a9b..03e6c3a9b 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/java/org/apache/guacamole/auth/vault/user/VaultUserContextFactory.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContextFactory.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.vault.user; +package org.apache.guacamole.vault.user; import org.apache.guacamole.net.auth.UserContext; diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/resources/translations/en.json b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/resources/translations/en.json similarity index 100% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-base/src/main/resources/translations/en.json rename to extensions/guacamole-vault/modules/guacamole-vault-base/src/main/resources/translations/en.json diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/.ratignore b/extensions/guacamole-vault/modules/guacamole-vault-dist/.ratignore similarity index 100% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/.ratignore rename to extensions/guacamole-vault/modules/guacamole-vault-dist/.ratignore diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/pom.xml b/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml similarity index 82% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/pom.xml rename to extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml index b239f28bf..abbba239c 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/pom.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml @@ -24,9 +24,9 @@ 4.0.0 org.apache.guacamole - guacamole-auth-vault-dist + guacamole-vault-dist pom - guacamole-auth-vault-dist + guacamole-vault-dist http://guacamole.apache.org/ @@ -35,7 +35,7 @@ org.apache.guacamole - guacamole-auth-vault + guacamole-vault 1.4.0 ../../ @@ -45,7 +45,7 @@ org.apache.guacamole - guacamole-auth-vault-azure + guacamole-vault-azure 1.4.0 @@ -53,9 +53,8 @@ - + ${project.parent.artifactId}-${project.parent.version} diff --git a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/src/main/assembly/dist.xml b/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml similarity index 95% rename from extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/src/main/assembly/dist.xml rename to extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml index b0ea3b1f6..8d5c4b866 100644 --- a/extensions/guacamole-auth-vault/modules/guacamole-auth-vault-dist/src/main/assembly/dist.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml @@ -37,7 +37,7 @@ azure - org.apache.guacamole:guacamole-auth-vault-azure + org.apache.guacamole:guacamole-vault-azure diff --git a/extensions/guacamole-auth-vault/pom.xml b/extensions/guacamole-vault/pom.xml similarity index 88% rename from extensions/guacamole-auth-vault/pom.xml rename to extensions/guacamole-vault/pom.xml index a924af8b3..281572b3a 100644 --- a/extensions/guacamole-auth-vault/pom.xml +++ b/extensions/guacamole-vault/pom.xml @@ -24,10 +24,10 @@ 4.0.0 org.apache.guacamole - guacamole-auth-vault + guacamole-vault pom 1.4.0 - guacamole-auth-vault + guacamole-vault http://guacamole.apache.org/ @@ -40,13 +40,13 @@ - modules/guacamole-auth-vault-dist + modules/guacamole-vault-dist - modules/guacamole-auth-vault-base + modules/guacamole-vault-base - modules/guacamole-auth-vault-azure + modules/guacamole-vault-azure diff --git a/extensions/pom.xml b/extensions/pom.xml index 18e307e54..d400b08ff 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -48,7 +48,9 @@ guacamole-auth-quickconnect guacamole-auth-sso guacamole-auth-totp - guacamole-auth-vault + + + guacamole-vault From b57578ad8eddc438fd1edb051dde5ec7800299dc Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 12/36] GUACAMOLE-641: Rename vault-specific username token to "USERNAME" to avoid confusion with "GUAC_USERNAME". The "GUAC_USERNAME" token provided by the webapp is based off the username provided by the user when they authenticated. The username token provided by the vault extensions uses the username stored with the user's corresponding object, which may not be the same. --- .../apache/guacamole/vault/user/VaultUserContext.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java index 1adf5c54d..9772815cb 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java @@ -53,10 +53,13 @@ public class VaultUserContext extends TokenInjectingUserContext { /** * The name of the token which will be replaced with the username of the - * current user if specified within the name of a secret. This token - * applies to both connections and connection groups. + * current user if specified within the name of a secret. Unlike the + * standard GUAC_USERNAME token, the username stored with the object + * representing the user is used here, not necessarily the username + * provided during authentication. This token applies to both connections + * and connection groups. */ - private static final String USERNAME_TOKEN = "GUAC_USERNAME"; + private static final String USERNAME_TOKEN = "USERNAME"; /** * The name of the token which will be replaced with the name of the @@ -139,7 +142,7 @@ public class VaultUserContext extends TokenInjectingUserContext { /** * Creates a new TokenFilter instance with token values set for all tokens * which are not specific to connections or connection groups. Currently, - * this is only the username token ("GUAC_USERNAME"). + * this is only the vault-specific username token ("USERNAME"). * * @return * A new TokenFilter instance with token values set for all tokens From 5aba0cd09da229634f4cf91da78fe4f00288fd91 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 13/36] GUACAMOLE-641: Read token/secret mapping from YAML instead of JSON. --- .../conf/AzureKeyVaultConfigurationService.java | 8 ++++---- .../modules/guacamole-vault-base/pom.xml | 6 +++++- .../vault/conf/VaultConfigurationService.java | 17 +++++++++-------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java index 7bfb52919..bed7b264f 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java @@ -42,10 +42,10 @@ public class AzureKeyVaultConfigurationService extends VaultConfigurationService private Environment environment; /** - * The name of the file which contains the JSON mapping of connection + * The name of the file which contains the YAML mapping of connection * parameter token to Azure Key Vault secret name. */ - private static final String TOKEN_MAPPING_FILENAME = "azure-keyvault-token-mapping.json"; + private static final String TOKEN_MAPPING_FILENAME = "azure-keyvault-token-mapping.yml"; /** * The number of milliseconds that each retrieved secret should be cached @@ -101,8 +101,8 @@ public class AzureKeyVaultConfigurationService extends VaultConfigurationService /** * Creates a new AzureKeyVaultConfigurationService which reads the token - * mapping from "azure-keyvault-token-mapping.json". The token mapping is - * a JSON file which lists each connection parameter token and the name of + * mapping from "azure-keyvault-token-mapping.yml". The token mapping is a + * YAML file which lists each connection parameter token and the name of * the secret from which the value for that token should be read. */ public AzureKeyVaultConfigurationService() { diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/pom.xml b/extensions/guacamole-vault/modules/guacamole-vault-base/pom.xml index 87c6c9404..96fe1180a 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/pom.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/pom.xml @@ -49,11 +49,15 @@ provided - + com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java index 9c01f7030..b398c4919 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java @@ -21,6 +21,7 @@ package org.apache.guacamole.vault.conf; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.inject.Inject; import java.io.File; import java.io.IOException; @@ -46,22 +47,22 @@ public abstract class VaultConfigurationService { private Environment environment; /** - * ObjectMapper for deserializing JSON. + * ObjectMapper for deserializing YAML. */ - private static final ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); /** - * The name of the file containing a JSON mapping of Guacamole parameter + * The name of the file containing a YAML mapping of Guacamole parameter * token to vault secret name. */ private final String tokenMappingFilename; /** * Creates a new VaultConfigurationService which retrieves the token/secret - * mapping from a JSON file having the given name. + * mapping from a YAML file having the given name. * * @param tokenMappingFilename - * The name of the JSON file containing the token/secret mapping. + * The name of the YAML file containing the token/secret mapping. */ protected VaultConfigurationService(String tokenMappingFilename) { this.tokenMappingFilename = tokenMappingFilename; @@ -84,19 +85,19 @@ public abstract class VaultConfigurationService { * parameter token. * * @throws GuacamoleException - * If the JSON file defining the token/secret mapping cannot be read. + * If the YAML file defining the token/secret mapping cannot be read. */ public Map getTokenMapping() throws GuacamoleException { // Get configuration file from GUACAMOLE_HOME File confFile = new File(environment.getGuacamoleHome(), tokenMappingFilename); - // Deserialize token mapping from JSON + // Deserialize token mapping from YAML try { return mapper.readValue(confFile, new TypeReference>() {}); } - // Fail if JSON is invalid/unreadable + // Fail if YAML is invalid/unreadable catch (IOException e) { throw new GuacamoleServerException("Unable to read token mapping " + "configuration file \"" + tokenMappingFilename + "\".", e); From 4d3b2a943550d6882a23c461ad2f6fb2e0a446f2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 14/36] GUACAMOLE-641: Obtain connection hostname and/or username for vault tokens via privileged access, if possible. --- .../vault/user/VaultUserContext.java | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java index 9772815cb..cf3d99107 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java @@ -32,7 +32,6 @@ import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.TokenInjectingUserContext; import org.apache.guacamole.net.auth.UserContext; -import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.apache.guacamole.token.GuacamoleTokenUndefinedException; import org.apache.guacamole.token.TokenFilter; import org.apache.guacamole.vault.conf.VaultConfigurationService; @@ -286,6 +285,41 @@ public class VaultUserContext extends TokenInjectingUserContext { } + /** + * Retrieves the connection parameters associated with the + * GuacamoleConfiguration of the given Connection. If possible, privileged + * access to those parameters is obtained first. Note that the underlying + * extension is not required to allow privileged access, nor is it + * required to expose the underlying connection parameters at all. + * + * @param connection + * The connection to retrieve parameters from. + * + * @return + * A Map of all connection parameters exposed by the underlying + * extension for the given connection, which may be empty. + * + * @throws GuacamoleException + * If an error prevents privileged retrieval of parameters. + */ + private Map getConnectionParameters(Connection connection) + throws GuacamoleException { + + String identifier = connection.getIdentifier(); + + // Obtain privileged access to parameters if possible (note that the + // UserContext returned by getPrivileged() is not guaranteed to + // actually be privileged) + Connection privilegedConnection = getPrivileged().getConnectionDirectory().get(identifier); + if (privilegedConnection != null) + return privilegedConnection.getConfiguration().getParameters(); + + // Fall back to unprivileged access if not implemented/allowed by + // extension + return connection.getConfiguration().getParameters(); + + } + @Override protected Map getTokens(Connection connection) throws GuacamoleException { @@ -303,9 +337,9 @@ public class VaultUserContext extends TokenInjectingUserContext { // Add hostname and username tokens if available (implementations are // not required to expose connection configuration details) - GuacamoleConfiguration config = connection.getConfiguration(); + Map parameters = getConnectionParameters(connection); - String hostname = config.getParameter("hostname"); + String hostname = parameters.get("hostname"); if (hostname != null) filter.setToken(CONNECTION_HOSTNAME_TOKEN, hostname); else @@ -314,7 +348,7 @@ public class VaultUserContext extends TokenInjectingUserContext { + "secret names.", identifier, name, CONNECTION_HOSTNAME_TOKEN); - String username = config.getParameter("username"); + String username = parameters.get("username"); if (username != null) filter.setToken(CONNECTION_USERNAME_TOKEN, username); else From 16cb9ed69b5917a9eeb37b727db287240bcea2af Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 15/36] GUACAMOLE-641: Expand CONNECTION_USERNAME and CONNECTION_HOSTNAME tokens only if corresponding parameters are non-empty. --- .../guacamole/vault/user/VaultUserContext.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java index cf3d99107..a9541aca2 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java @@ -77,14 +77,20 @@ public class VaultUserContext extends TokenInjectingUserContext { /** * The name of the token which will be replaced with the \"hostname\" * connection parameter of the current connection if specified within the - * name of a secret. This token only applies only to connections. + * name of a secret. If the \"hostname\" parameter cannot be retrieved, or + * if the parameter is blank, the token will not be replaced and any + * secrets involving that token will not be retrieved. This token only + * applies only to connections. */ private static final String CONNECTION_HOSTNAME_TOKEN = "CONNECTION_HOSTNAME"; /** * The name of the token which will be replaced with the \"username\" * connection parameter of the current connection if specified within the - * name of a secret. This token only applies only to connections. + * name of a secret. If the \"username\" parameter cannot be retrieved, or + * if the parameter is blank, the token will not be replaced and any + * secrets involving that token will not be retrieved. This token only + * applies only to connections. */ private static final String CONNECTION_USERNAME_TOKEN = "CONNECTION_USERNAME"; @@ -340,7 +346,7 @@ public class VaultUserContext extends TokenInjectingUserContext { Map parameters = getConnectionParameters(connection); String hostname = parameters.get("hostname"); - if (hostname != null) + if (hostname != null && !hostname.isEmpty()) filter.setToken(CONNECTION_HOSTNAME_TOKEN, hostname); else logger.debug("Hostname for connection \"{}\" (\"{}\") not " @@ -349,7 +355,7 @@ public class VaultUserContext extends TokenInjectingUserContext { CONNECTION_HOSTNAME_TOKEN); String username = parameters.get("username"); - if (username != null) + if (username != null && !username.isEmpty()) filter.setToken(CONNECTION_USERNAME_TOKEN, username); else logger.debug("Username for connection \"{}\" (\"{}\") not " From 786430612eb45b8812ae5727fdc7a07889feae92 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 16:34:23 -0800 Subject: [PATCH 16/36] GUACAMOLE-641: Canonicalize individual, tokenized components of secret names rather than the whole name. In the event that a secret name is structured, such as the URL-like notation used by Keeper Secrets Manager, canonicalizing/encoding the entire name could result in the name itself becoming invalid. Only the portions that come from tokens should be canonicalized. --- .../secret/AzureKeyVaultSecretService.java | 4 ++-- .../vault/secret/VaultSecretService.java | 18 +++++++-------- .../vault/user/VaultUserContext.java | 23 ++++++++++++++++--- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java index cd5ed3537..cf9faa4c3 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java @@ -66,8 +66,8 @@ public class AzureKeyVaultSecretService extends CachedVaultSecretService { * not allowed by Azure Key Vault, replacing them with a single dash. */ @Override - public String canonicalize(String name) { - Matcher disallowed = DISALLOWED_CHARACTERS.matcher(name); + public String canonicalize(String nameComponent) { + Matcher disallowed = DISALLOWED_CHARACTERS.matcher(nameComponent); return disallowed.replaceAll("-"); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java index 04f3783df..1ff230ca5 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java @@ -29,25 +29,25 @@ public interface VaultSecretService { /** * Translates an arbitrary string, which may contain characters not allowed - * by the vault implementation, into a string which is a valid secret name. - * The type of transformation performed on the string, if any, will depend - * on the specific requirements of the vault provider. + * by the vault implementation, into a string which is valid within a + * secret name. The type of transformation performed on the string, if any, + * will depend on the specific requirements of the vault provider. * * NOTE: It is critical that this transformation is deterministic and * reasonably predictable for users. If an implementation must apply a * transformation to secret names, that transformation needs to be * documented. * - * @param name - * An arbitrary string intended for use as a secret name, but which may - * contain characters not allowed by the vault implementation. + * @param nameComponent + * An arbitrary string intended for use within a secret name, but which + * may contain characters not allowed by the vault implementation. * * @return - * A name containing essentially the same content as the provided + * A string containing essentially the same content as the provided * string, but transformed deterministically such that it is acceptable - * as a secret name by the vault provider. + * as a component of a secret name by the vault provider. */ - String canonicalize(String name); + String canonicalize(String nameComponent); /** * Returns a Future which eventually completes with the value of the secret diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java index a9541aca2..1367ac438 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java @@ -147,14 +147,31 @@ public class VaultUserContext extends TokenInjectingUserContext { /** * Creates a new TokenFilter instance with token values set for all tokens * which are not specific to connections or connection groups. Currently, - * this is only the vault-specific username token ("USERNAME"). + * this is only the vault-specific username token ("USERNAME"). Each token + * stored within the returned TokenFilter via setToken() will be + * automatically canonicalized for use within secret names. * * @return * A new TokenFilter instance with token values set for all tokens * which are not specific to connections or connection groups. */ private TokenFilter createFilter() { - TokenFilter filter = new TokenFilter(); + + // Create filter that automatically canonicalizes all token values + TokenFilter filter = new TokenFilter() { + + @Override + public void setToken(String name, String value) { + super.setToken(name, secretService.canonicalize(value)); + } + + @Override + public void setTokens(Map tokens) { + tokens.entrySet().forEach((entry) -> setToken(entry.getKey(), entry.getValue())); + } + + }; + filter.setToken(USERNAME_TOKEN, self().getIdentifier()); return filter; } @@ -196,7 +213,7 @@ public class VaultUserContext extends TokenInjectingUserContext { // secrets which cannot be translated String secretName; try { - secretName = secretService.canonicalize(filter.filterStrict(entry.getValue())); + secretName = filter.filterStrict(entry.getValue()); } catch (GuacamoleTokenUndefinedException e) { logger.debug("Secret for token \"{}\" will not be retrieved. " From 8bedbe746c180ab3f16fdc2ba9c76e5c1ab394c7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 17/36] GUACAMOLE-641: Add initial draft implementation of KSM vault support for Guacamole. --- .../bouncycastle-fips-1.0.2.1/LICENSE | 20 + doc/licenses/bouncycastle-fips-1.0.2.1/README | 8 + .../dep-coordinates.txt | 1 + .../jetbrains-annotations-13.0/README | 9 + .../dep-coordinates.txt | 1 + doc/licenses/kotlin-1.5.30/NOTICE.txt | 2 + doc/licenses/kotlin-1.5.30/README | 8 + .../kotlin-1.5.30/dep-coordinates.txt | 5 + .../kotlinx-serialization-1.2.1/NOTICE.txt | 2 + .../kotlinx-serialization-1.2.1/README | 8 + .../dep-coordinates.txt | 2 + doc/licenses/ksm-sdk-16.2.1/LICENSE | 21 + doc/licenses/ksm-sdk-16.2.1/README | 9 + .../ksm-sdk-16.2.1/dep-coordinates.txt | 1 + .../modules/guacamole-vault-ksm/.ratignore | 0 .../modules/guacamole-vault-ksm/pom.xml | 98 ++++ .../vault/ksm/KsmAuthenticationProvider.java | 47 ++ .../ksm/KsmAuthenticationProviderModule.java | 56 ++ .../vault/ksm/conf/KsmConfigProperty.java | 53 ++ .../ksm/conf/KsmConfigurationService.java | 116 +++++ .../guacamole/vault/ksm/secret/KsmClient.java | 491 ++++++++++++++++++ .../vault/ksm/secret/KsmSecretService.java | 62 +++ .../src/main/resources/guac-manifest.json | 16 + extensions/guacamole-vault/pom.xml | 1 + 24 files changed, 1037 insertions(+) create mode 100644 doc/licenses/bouncycastle-fips-1.0.2.1/LICENSE create mode 100644 doc/licenses/bouncycastle-fips-1.0.2.1/README create mode 100644 doc/licenses/bouncycastle-fips-1.0.2.1/dep-coordinates.txt create mode 100644 doc/licenses/jetbrains-annotations-13.0/README create mode 100644 doc/licenses/jetbrains-annotations-13.0/dep-coordinates.txt create mode 100644 doc/licenses/kotlin-1.5.30/NOTICE.txt create mode 100644 doc/licenses/kotlin-1.5.30/README create mode 100644 doc/licenses/kotlin-1.5.30/dep-coordinates.txt create mode 100644 doc/licenses/kotlinx-serialization-1.2.1/NOTICE.txt create mode 100644 doc/licenses/kotlinx-serialization-1.2.1/README create mode 100644 doc/licenses/kotlinx-serialization-1.2.1/dep-coordinates.txt create mode 100644 doc/licenses/ksm-sdk-16.2.1/LICENSE create mode 100644 doc/licenses/ksm-sdk-16.2.1/README create mode 100644 doc/licenses/ksm-sdk-16.2.1/dep-coordinates.txt create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/.ratignore create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/pom.xml create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProvider.java create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProviderModule.java create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigProperty.java create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigurationService.java create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/resources/guac-manifest.json diff --git a/doc/licenses/bouncycastle-fips-1.0.2.1/LICENSE b/doc/licenses/bouncycastle-fips-1.0.2.1/LICENSE new file mode 100644 index 000000000..a02bc176b --- /dev/null +++ b/doc/licenses/bouncycastle-fips-1.0.2.1/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2000 - 2021 The Legion of the Bouncy Castle Inc. +(https://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/licenses/bouncycastle-fips-1.0.2.1/README b/doc/licenses/bouncycastle-fips-1.0.2.1/README new file mode 100644 index 000000000..4d92a89ee --- /dev/null +++ b/doc/licenses/bouncycastle-fips-1.0.2.1/README @@ -0,0 +1,8 @@ +BouncyCastle FIPS Distribution (https://www.bouncycastle.org/fips-java) +----------------------------------------------------------------------- + + Version: 1.0.2.1 + From: 'The Legion of Bouncy Castle' (https://www.bouncycastle.org) + License(s): + MIT (bundled/bouncycastle-fips-1.0.2.1/LICENSE) + diff --git a/doc/licenses/bouncycastle-fips-1.0.2.1/dep-coordinates.txt b/doc/licenses/bouncycastle-fips-1.0.2.1/dep-coordinates.txt new file mode 100644 index 000000000..854f49a58 --- /dev/null +++ b/doc/licenses/bouncycastle-fips-1.0.2.1/dep-coordinates.txt @@ -0,0 +1 @@ +org.bouncycastle:bc-fips:jar:1.0.2.1 diff --git a/doc/licenses/jetbrains-annotations-13.0/README b/doc/licenses/jetbrains-annotations-13.0/README new file mode 100644 index 000000000..e09618cab --- /dev/null +++ b/doc/licenses/jetbrains-annotations-13.0/README @@ -0,0 +1,9 @@ +Annotations for JVM-based languages +(https://github.com/JetBrains/java-annotations) +----------------------------------------------- + + Version: 13.0 + From: 'JetBrains s.r.o.' (http://www.jetbrains.com) + License(s): + Apache v2.0 + diff --git a/doc/licenses/jetbrains-annotations-13.0/dep-coordinates.txt b/doc/licenses/jetbrains-annotations-13.0/dep-coordinates.txt new file mode 100644 index 000000000..bb558cd6c --- /dev/null +++ b/doc/licenses/jetbrains-annotations-13.0/dep-coordinates.txt @@ -0,0 +1 @@ +org.jetbrains:annotations:jar:13.0 diff --git a/doc/licenses/kotlin-1.5.30/NOTICE.txt b/doc/licenses/kotlin-1.5.30/NOTICE.txt new file mode 100644 index 000000000..3efb9a198 --- /dev/null +++ b/doc/licenses/kotlin-1.5.30/NOTICE.txt @@ -0,0 +1,2 @@ +Kotlin Compiler +Copyright 2010-2020 JetBrains s.r.o and respective authors and developers diff --git a/doc/licenses/kotlin-1.5.30/README b/doc/licenses/kotlin-1.5.30/README new file mode 100644 index 000000000..f2de926e9 --- /dev/null +++ b/doc/licenses/kotlin-1.5.30/README @@ -0,0 +1,8 @@ +Kotlin (https://kotlinlang.org/) +-------------------------------- + + Version: 1.5.30 + From: 'JetBrains s.r.o and respective authors and developers' + License(s): + Apache v2.0 + diff --git a/doc/licenses/kotlin-1.5.30/dep-coordinates.txt b/doc/licenses/kotlin-1.5.30/dep-coordinates.txt new file mode 100644 index 000000000..474a373f0 --- /dev/null +++ b/doc/licenses/kotlin-1.5.30/dep-coordinates.txt @@ -0,0 +1,5 @@ +org.jetbrains.kotlin:kotlin-reflect:jar:1.5.30 +org.jetbrains.kotlin:kotlin-stdlib:jar:1.5.30 +org.jetbrains.kotlin:kotlin-stdlib-common:jar:1.5.30 +org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:1.5.30 +org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:1.5.30 diff --git a/doc/licenses/kotlinx-serialization-1.2.1/NOTICE.txt b/doc/licenses/kotlinx-serialization-1.2.1/NOTICE.txt new file mode 100644 index 000000000..16f7e9bee --- /dev/null +++ b/doc/licenses/kotlinx-serialization-1.2.1/NOTICE.txt @@ -0,0 +1,2 @@ +kotlinx.serialization library. +Copyright 2017-2019 JetBrains s.r.o and respective authors and developers diff --git a/doc/licenses/kotlinx-serialization-1.2.1/README b/doc/licenses/kotlinx-serialization-1.2.1/README new file mode 100644 index 000000000..5ded4539e --- /dev/null +++ b/doc/licenses/kotlinx-serialization-1.2.1/README @@ -0,0 +1,8 @@ +Kotlin Serialization (https://github.com/Kotlin/kotlinx.serialization) +---------------------------------------------------------------------- + + Version: 1.2.1 + From: 'JetBrains s.r.o and respective authors and developers' + License(s): + Apache v2.0 + diff --git a/doc/licenses/kotlinx-serialization-1.2.1/dep-coordinates.txt b/doc/licenses/kotlinx-serialization-1.2.1/dep-coordinates.txt new file mode 100644 index 000000000..e6fe5522d --- /dev/null +++ b/doc/licenses/kotlinx-serialization-1.2.1/dep-coordinates.txt @@ -0,0 +1,2 @@ +org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:jar:1.2.1 +org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:jar:1.2.1 diff --git a/doc/licenses/ksm-sdk-16.2.1/LICENSE b/doc/licenses/ksm-sdk-16.2.1/LICENSE new file mode 100644 index 000000000..b63322a17 --- /dev/null +++ b/doc/licenses/ksm-sdk-16.2.1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Keeper Security + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/licenses/ksm-sdk-16.2.1/README b/doc/licenses/ksm-sdk-16.2.1/README new file mode 100644 index 000000000..4bf0527cf --- /dev/null +++ b/doc/licenses/ksm-sdk-16.2.1/README @@ -0,0 +1,9 @@ +Keeper Secrets Manager Java SDK +(https://github.com/Keeper-Security/secrets-manager) +---------------------------------------------------- + + Version: 16.2.1 + From: 'Keeper Security' (https://www.keepersecurity.com/) + License(s): + MIT (bundled/ksm-sdk-16.2.1/LICENSE) + diff --git a/doc/licenses/ksm-sdk-16.2.1/dep-coordinates.txt b/doc/licenses/ksm-sdk-16.2.1/dep-coordinates.txt new file mode 100644 index 000000000..5b9330316 --- /dev/null +++ b/doc/licenses/ksm-sdk-16.2.1/dep-coordinates.txt @@ -0,0 +1 @@ +com.keepersecurity.secrets-manager:core:jar:16.2.1 diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/.ratignore b/extensions/guacamole-vault/modules/guacamole-vault-ksm/.ratignore new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/pom.xml b/extensions/guacamole-vault/modules/guacamole-vault-ksm/pom.xml new file mode 100644 index 000000000..ac3b75f9a --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/pom.xml @@ -0,0 +1,98 @@ + + + + + 4.0.0 + org.apache.guacamole + guacamole-vault-ksm + jar + 1.4.0 + guacamole-vault-ksm + http://guacamole.apache.org/ + + + org.apache.guacamole + guacamole-vault + 1.4.0 + ../../ + + + + 1.5.30 + + + + + + + org.apache.guacamole + guacamole-ext + provided + + + + + org.apache.guacamole + guacamole-vault-base + 1.4.0 + + + + com.keepersecurity.secrets-manager + core + 16.2.1 + + + + + org.jetbrains.kotlin + kotlin-stdlib + + + org.jetbrains.kotlin + kotlin-stdlib-common + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + + + diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProvider.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProvider.java new file mode 100644 index 000000000..a24dfc875 --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProvider.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.vault.ksm; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.vault.VaultAuthenticationProvider; + +/** + * VaultAuthenticationProvider implementation which reads secrets from Keeper + * Secrets Manager + */ +public class KsmAuthenticationProvider extends VaultAuthenticationProvider { + + /** + * Creates a new KsmKeyVaultAuthenticationProvider which reads secrets + * from a configured Keeper Secrets Manager. + * + * @throws GuacamoleException + * If configuration details cannot be read from guacamole.properties. + */ + public KsmAuthenticationProvider() throws GuacamoleException { + super(new KsmAuthenticationProviderModule()); + } + + @Override + public String getIdentifier() { + return "keeper-secrets-manager"; + } + +} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProviderModule.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProviderModule.java new file mode 100644 index 000000000..df3adf8c6 --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProviderModule.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.vault.ksm; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.vault.VaultAuthenticationProviderModule; +import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService; +import org.apache.guacamole.vault.ksm.secret.KsmSecretService; +import org.apache.guacamole.vault.conf.VaultConfigurationService; +import org.apache.guacamole.vault.ksm.secret.KsmClient; +import org.apache.guacamole.vault.secret.VaultSecretService; + +/** + * Guice module which configures injections specific to Keeper Secrets + * Manager support. + */ +public class KsmAuthenticationProviderModule + extends VaultAuthenticationProviderModule { + + /** + * Creates a new KsmAuthenticationProviderModule which + * configures dependency injection for the Keeper Secrets Manager + * authentication provider and related services. + * + * @throws GuacamoleException + * If configuration details in guacamole.properties cannot be parsed. + */ + public KsmAuthenticationProviderModule() throws GuacamoleException {} + + @Override + protected void configureVault() { + + // Bind services specific to Keeper Secrets Manager + bind(KsmClient.class); + bind(VaultConfigurationService.class).to(KsmConfigurationService.class); + bind(VaultSecretService.class).to(KsmSecretService.class); + } + +} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigProperty.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigProperty.java new file mode 100644 index 000000000..aaddb0de0 --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigProperty.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.vault.ksm.conf; + +import com.keepersecurity.secretsManager.core.InMemoryStorage; +import com.keepersecurity.secretsManager.core.KeyValueStorage; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.properties.GuacamoleProperty; + +/** + * A GuacamoleProperty whose value is Keeper Secrets Manager {@link KeyValueStorage} + * object. The value of this property must be base64-encoded JSON, as output by + * the Keeper Commander CLI tool via the "sm client add" command. + */ +public abstract class KsmConfigProperty implements GuacamoleProperty { + + @Override + public KeyValueStorage parseValue(String value) throws GuacamoleException { + + // If no property provided, return null. + if (value == null) + return null; + + // Parse base64 value as KSM config storage + try { + return new InMemoryStorage(value); + } + catch (IllegalArgumentException e) { + throw new GuacamoleServerException("Invalid base64 configuration " + + "for Keeper Secrets Manager.", e); + } + + } + +} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigurationService.java new file mode 100644 index 000000000..398b9bb3a --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigurationService.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.vault.ksm.conf; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.BooleanGuacamoleProperty; +import org.apache.guacamole.vault.conf.VaultConfigurationService; +import com.keepersecurity.secretsManager.core.SecretsManagerOptions; + +/** + * Service for retrieving configuration information regarding the Keeper + * Secrets Manager authentication extension. + */ +@Singleton +public class KsmConfigurationService extends VaultConfigurationService { + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /** + * The name of the file which contains the YAML mapping of connection + * parameter token to secrets within Keeper Secrets Manager. + */ + private static final String TOKEN_MAPPING_FILENAME = "ksm-token-mapping.yml"; + + /** + * The base64-encoded configuration information generated by the Keeper + * Commander CLI tool. + */ + private static final KsmConfigProperty KSM_CONFIG = new KsmConfigProperty() { + + @Override + public String getName() { + return "ksm-config"; + } + }; + + /** + * Whether unverified server certificates should be accepted. + */ + private static final BooleanGuacamoleProperty ALLOW_UNVERIFIED_CERT = new BooleanGuacamoleProperty() { + + @Override + public String getName() { + return "ksm-allow-unverified-cert"; + } + }; + + /** + * Creates a new KsmConfigurationService which reads the configuration + * from "ksm-token-mapping.yml". The token mapping is a YAML file which + * lists each connection parameter token and the title of the secret from + * which the value for that token should be read. + */ + public KsmConfigurationService() { + super(TOKEN_MAPPING_FILENAME); + } + + /** + * Return whether unverified server certificates should be accepted when + * communicating with Keeper Secrets Manager. + * + * @return + * true if unverified server certificates should be accepted, false + * otherwise. + * + * @throws GuacamoleException + * If the value specified within guacamole.properties cannot be + * parsed. + */ + public boolean getAllowUnverifiedCertificate() throws GuacamoleException { + return environment.getProperty(ALLOW_UNVERIFIED_CERT, false); + } + + /** + * Returns the options required to authenticate with Keeper Secrets Manager + * when retrieving secrets. These options are read from the contents of + * base64-encoded JSON configuration data generated by the Keeper Commander + * CLI tool. + * + * @return + * The options that should be used when connecting to Keeper Secrets + * Manager when retrieving secrets. + * + * @throws GuacamoleException + * If required properties are not specified within + * guacamole.properties or cannot be parsed. + */ + public SecretsManagerOptions getSecretsManagerOptions() throws GuacamoleException { + return new SecretsManagerOptions(environment.getRequiredProperty(KSM_CONFIG), null, + getAllowUnverifiedCertificate()); + } +} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java new file mode 100644 index 000000000..a72a2fdaa --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java @@ -0,0 +1,491 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.vault.ksm.secret; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.keepersecurity.secretsManager.core.Hosts; +import com.keepersecurity.secretsManager.core.KeeperFile; +import com.keepersecurity.secretsManager.core.KeeperRecord; +import com.keepersecurity.secretsManager.core.KeeperRecordData; +import com.keepersecurity.secretsManager.core.KeeperRecordField; +import com.keepersecurity.secretsManager.core.KeeperSecrets; +import com.keepersecurity.secretsManager.core.Login; +import com.keepersecurity.secretsManager.core.Notation; +import com.keepersecurity.secretsManager.core.SecretsManager; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Client which retrieves records from Keeper Secrets Manager, allowing + * content-based record retrieval. Note that because KSM is zero-knowledge, + * searching or indexing based on content can only be accomplished by + * retrieving and indexing everything. Except for record UIDs (which contain no + * information), it's not possible for the server to perform a search of + * content on the client's behalf. The client has to perform its own search. + */ +@Singleton +public class KsmClient { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(KsmClient.class); + + /** + * Service for retrieving configuration information. + */ + @Inject + private KsmConfigurationService confService; + + /** + * The publicly-accessible URL for Keeper's documentation covering Keeper + * notation. + */ + private static final String KEEPER_NOTATION_DOC_URL = + "https://docs.keeper.io/secrets-manager/secrets-manager/about/keeper-notation"; + + /** + * The regular expression that Keeper notation must match to be related to + * file retrieval. As the Keeper SDK provides mutually-exclusive for + * retrieving secret values and files via notation, the notation must first + * be tested to determine whether it refers to a file. + */ + private static final Pattern KEEPER_FILE_NOTATION = Pattern.compile("^(keeper://)?[^/]*/file/.+"); + + /** + * The maximum amount of time that an entry will be stored in the cache + * before being refreshed, in milliseconds. + */ + private static final long CACHE_INTERVAL = 5000; + + /** + * Read/write lock which guards access to all cached data, including the + * timestamp recording the last time the cache was refreshed. Readers of + * the cache must first acquire (and eventually release) the read lock, and + * writers of the cache must first acquire (and eventually release) the + * write lock. + */ + private final ReadWriteLock cacheLock = new ReentrantReadWriteLock(); + + /** + * The timestamp that the cache was last refreshed, in milliseconds, as + * returned by System.currentTimeMillis(). This value is automatically + * updated if {@link #validateCache()} refreshes the cache. This value must + * not be accessed without {@link #cacheLock} acquired appropriately. + */ + private volatile long cacheTimestamp = 0; + + /** + * The full cached set of secrets last retrieved from Keeper Secrets + * Manager. This value is automatically updated if {@link #validateCache()} + * refreshes the cache. This value must not be accessed without + * {@link #cacheLock} acquired appropriately. + */ + private KeeperSecrets cachedSecrets = null; + + /** + * All records retrieved from Keeper Secrets Manager, where each key is the + * UID of the corresponding record. The contents of this Map are + * automatically updated if {@link #validateCache()} refreshes the cache. + * This Map must not be accessed without {@link #cacheLock} acquired + * appropriately. + */ + private final Map cachedRecordsByUid = new HashMap<>(); + + /** + * All records retrieved from Keeper Secrets Manager, where each key is the + * hostname or IP address of the corresponding record. The hostname or IP + * address of a record is determined by {@link Hosts} fields, thus a record + * may be associated with multiple hosts. If a record is associated with + * multiple hosts, there will be multiple references to that record within + * this Map. The contents of this Map are automatically updated if + * {@link #validateCache()} refreshes the cache. This Map must not be + * accessed without {@link #cacheLock} acquired appropriately. Before using + * a value from this Map, {@link #cachedAmbiguousHosts} must first be + * checked to verify that there is indeed only one record associated with + * that host. + */ + private final Map cachedRecordsByHost = new HashMap<>(); + + /** + * The set of all hostnames or IP addresses that are associated with + * multiple records, and thus cannot uniquely identify a record. The + * contents of this Set are automatically updated if + * {@link #validateCache()} refreshes the cache. This Set must not be + * accessed without {@link #cacheLock} acquired appropriately.This Set + * must be checked before using a value retrieved from + * {@link #cachedRecordsByHost}. + */ + private final Set cachedAmbiguousHosts = new HashSet<>(); + + /** + * All records retrieved from Keeper Secrets Manager, where each key is the + * username of the corresponding record. The username of a record is + * determined by {@link Login} fields, thus a record may be associated with + * multiple users. If a record is associated with multiple users, there + * will be multiple references to that record within this Map. The contents + * of this Map are automatically updated if {@link #validateCache()} + * refreshes the cache. This Map must not be accessed without + * {@link #cacheLock} acquired appropriately. Before using a value from + * this Map, {@link #cachedAmbiguousUsernames} must first be checked to + * verify that there is indeed only one record associated with that user. + */ + private final Map cachedRecordsByUsername = new HashMap<>(); + + /** + * The set of all usernames that are associated with multiple records, and + * thus cannot uniquely identify a record. The contents of this Set are + * automatically updated if {@link #validateCache()} refreshes the cache. + * This Set must not be accessed without {@link #cacheLock} acquired + * appropriately.This Set must be checked before using a value retrieved + * from {@link #cachedRecordsByUsername}. + */ + private final Set cachedAmbiguousUsernames = new HashSet<>(); + + /** + * Validates that all cached data is current with respect to + * {@link #CACHE_INTERVAL}, refreshing data from the server as needed. + * + * @throws GuacamoleException + * If an error occurs preventing the cached data from being refreshed. + */ + private void validateCache() throws GuacamoleException { + + long currentTime = System.currentTimeMillis(); + + // Perform a read-only check that the cache has actually expired before + // continuing + cacheLock.readLock().lock(); + try { + if (currentTime - cacheTimestamp < CACHE_INTERVAL) + return; + } + finally { + cacheLock.readLock().unlock(); + } + + cacheLock.writeLock().lock(); + try { + + // Cache may have been updated since the read-only check. Re-verify + // that the cache has expired before continuing with a full refresh + if (currentTime - cacheTimestamp < CACHE_INTERVAL) + return; + + // Attempt to pull all records first, allowing that operation to + // succeed/fail BEFORE we clear out the last cached success + KeeperSecrets secrets = SecretsManager.getSecrets(confService.getSecretsManagerOptions()); + List records = secrets.getRecords(); + + // Store all secrets within cache + cachedSecrets = secrets; + + // Clear unambiguous cache of all records by UID + cachedRecordsByUid.clear(); + + // Clear cache of host-based records + cachedAmbiguousHosts.clear(); + cachedRecordsByHost.clear(); + + // Clear cache of login-based records + cachedAmbiguousUsernames.clear(); + cachedRecordsByUsername.clear(); + + // Store all records, sorting each into host-based and login-based + // buckets (note that a single record may be associated with + // multiple hosts and logins) + records.forEach(record -> { + + // Store based on UID ... + cachedRecordsByUid.put(record.getRecordUid(), record); + + // ... and standard fields ... + KeeperRecordData data = record.getData(); + addRecordForHosts(record, (Hosts) data.getField(Hosts.class)); + addRecordForLogin(record, (Login) data.getField(Login.class)); + + // ... and custom fields + List custom = data.getCustom(); + if (custom != null) { + custom.forEach(field -> { + if (field instanceof Hosts) + addRecordForHosts(record, (Hosts) field); + else if (field instanceof Login) + addRecordForLogin(record, (Login) field); + }); + } + + }); + + // Cache has been refreshed + this.cacheTimestamp = System.currentTimeMillis(); + + } + finally { + cacheLock.writeLock().unlock(); + } + + } + + /** + * Associates the given record with each of the hosts in the given Hosts + * field. The given Hosts field may be null. Both {@link #cachedRecordsByHost} + * and {@link #cachedAmbiguousHosts} are updated appropriately. The write + * lock of {@link #cacheLock} must already be acquired before invoking this + * function. + * + * @param record + * The record to associate with the hosts in the given field. + * + * @param hosts + * The Hosts field containing the hosts that the given record should be + * associated with. This may be null. + */ + private void addRecordForHosts(KeeperRecord record, Hosts hosts) { + + if (hosts == null) + return; + + hosts.getValue().stream().map(host -> host.getHostName()) + .forEachOrdered(hostname -> { + + KeeperRecord existing = cachedRecordsByHost.putIfAbsent(hostname, record); + if (existing != null && record != existing) + cachedAmbiguousHosts.add(hostname); + + }); + + } + + /** + * Associates the given record with each of the usernames in the given + * Login field. The given Hosts field may be null. Both + * {@link #cachedRecordsByUsername} and {@link #cachedAmbiguousUsernames} + * are updated appropriately. The write lock of {@link #cacheLock} must + * already be acquired before invoking this function. + * + * @param record + * The record to associate with the hosts in the given field. + * + * @param login + * The Login field containing the usernames that the given record + * should be associated with. This may be null. + */ + private void addRecordForLogin(KeeperRecord record, Login login) { + + if (login == null) + return; + + login.getValue().stream() + .forEachOrdered(username -> { + + KeeperRecord existing = cachedRecordsByUsername.putIfAbsent(username, record); + if (existing != null && record != existing) + cachedAmbiguousUsernames.add(username); + + }); + + } + + /** + * Returns all records accessible via Keeper Secrets Manager. The records + * returned are arbitrarily ordered. + * + * @return + * An unmodifiable Collection of all records accessible via Keeper + * Secrets Manager, in no particular order. + * + * @throws GuacamoleException + * If an error occurs that prevents records from being retrieved. + */ + public Collection getRecords() throws GuacamoleException { + validateCache(); + cacheLock.readLock().lock(); + try { + return Collections.unmodifiableCollection(cachedRecordsByUid.values()); + } + finally { + cacheLock.readLock().unlock(); + } + } + + /** + * Returns the record having the given UID. If no such record exists, null + * is returned. + * + * @param uid + * The UID of the record to return. + * + * @return + * The record having the given UID, or null if there is no such record. + * + * @throws GuacamoleException + * If an error occurs that prevents the record from being retrieved. + */ + public KeeperRecord getRecord(String uid) throws GuacamoleException { + validateCache(); + cacheLock.readLock().lock(); + try { + return cachedRecordsByUid.get(uid); + } + finally { + cacheLock.readLock().unlock(); + } + } + + /** + * Returns the record associated with the given hostname or IP address. If + * no such record exists, or there are multiple such records, null is + * returned. + * + * @param hostname + * The hostname of the record to return. + * + * @return + * The record associated with the given hostname, or null if there is + * no such record or multiple such records. + * + * @throws GuacamoleException + * If an error occurs that prevents the record from being retrieved. + */ + public KeeperRecord getRecordByHost(String hostname) throws GuacamoleException { + validateCache(); + cacheLock.readLock().lock(); + try { + + if (cachedAmbiguousHosts.contains(hostname)) + return null; + + return cachedRecordsByHost.get(hostname); + + } + finally { + cacheLock.readLock().unlock(); + } + } + + /** + * Returns the record associated with the given username. If no such record + * exists, or there are multiple such records, null is returned. + * + * @param username + * The username of the record to return. + * + * @return + * The record associated with the given username, or null if there is + * no such record or multiple such records. + * + * @throws GuacamoleException + * If an error occurs that prevents the record from being retrieved. + */ + public KeeperRecord getRecordByLogin(String username) throws GuacamoleException { + validateCache(); + cacheLock.readLock().lock(); + try { + + if (cachedAmbiguousUsernames.contains(username)) + return null; + + return cachedRecordsByUsername.get(username); + + } + finally { + cacheLock.readLock().unlock(); + } + } + + /** + * Returns the value of the secret stored within Keeper Secrets Manager and + * represented by the given Keeper notation. Keeper notation locates the + * value of a specific field, custom field, or file associated with a + * specific record. See: https://docs.keeper.io/secrets-manager/secrets-manager/about/keeper-notation + * + * @param notation + * The Keeper notation of the secret to retrieve. + * + * @return + * A Future which completes with the value of the secret represented by + * the given Keeper notation, or null if there is no such secret. + * + * @throws GuacamoleException + * If the requested secret cannot be retrieved or the Keeper notation + * is invalid. + */ + public Future getSecret(String notation) throws GuacamoleException { + validateCache(); + cacheLock.readLock().lock(); + try { + + Matcher fileNotationMatcher = KEEPER_FILE_NOTATION.matcher(notation); + if (fileNotationMatcher.matches()) { + + // Retrieve any relevant file asynchronously + KeeperFile file = Notation.getFile(cachedSecrets, notation); + return CompletableFuture.supplyAsync(() -> { + return new String(SecretsManager.downloadFile(file), StandardCharsets.UTF_8); + }); + + } + + // Retrieve string values synchronously + return CompletableFuture.completedFuture(Notation.getValue(cachedSecrets, notation)); + + } + + // Unfortunately, the notation parser within the Keeper SDK throws + // plain Errors for retrieval failures ... + catch (Error e) { + logger.warn("Record \"{}\" does not exist.", notation); + logger.debug("Retrieval of record by Keeper notation failed.", e); + return CompletableFuture.completedFuture(null); + } + + // ... and plain Exceptions for parse failures (no subclasses) + catch (Exception e) { + logger.warn("\"{}\" is not valid Keeper notation. Please check " + + "the documentation at {} for valid formatting.", + notation, KEEPER_NOTATION_DOC_URL); + logger.debug("Provided Keeper notation could not be parsed.", e); + return CompletableFuture.completedFuture(null); + } + finally { + cacheLock.readLock().unlock(); + } + + } + +} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java new file mode 100644 index 000000000..c8ae0d7e3 --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.vault.ksm.secret; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.concurrent.Future; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.vault.secret.VaultSecretService; + +/** + * Service which retrieves secrets from Keeper Secrets Manager. + */ +@Singleton +public class KsmSecretService implements VaultSecretService { + + /** + * Client for retrieving records and secrets from Keeper Secrets Manager. + */ + @Inject + private KsmClient ksm; + + @Override + public String canonicalize(String nameComponent) { + try { + + // As Keeper notation is essentially a URL, encode all components + // using standard URL escaping + return URLEncoder.encode(nameComponent, "UTF-8"); + + } + catch (UnsupportedEncodingException e) { + throw new UnsupportedOperationException("Unexpected lack of UTF-8 support.", e); + } + } + + @Override + public Future getValue(String name) throws GuacamoleException { + return ksm.getSecret(name); + } + +} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/resources/guac-manifest.json b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..a584b88b8 --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/resources/guac-manifest.json @@ -0,0 +1,16 @@ +{ + + "guacamoleVersion" : "1.4.0", + + "name" : "Keeper Secrets Manager", + "namespace" : "keeper-secrets-manager", + + "authProviders" : [ + "org.apache.guacamole.vault.ksm.KsmAuthenticationProvider" + ], + + "translations" : [ + "translations/en.json" + ] + +} diff --git a/extensions/guacamole-vault/pom.xml b/extensions/guacamole-vault/pom.xml index 281572b3a..80eb3da57 100644 --- a/extensions/guacamole-vault/pom.xml +++ b/extensions/guacamole-vault/pom.xml @@ -47,6 +47,7 @@ modules/guacamole-vault-azure + modules/guacamole-vault-ksm From d0043e34ddaf7510d88267fb4190a0672b104e3a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 18/36] GUACAMOLE-641: Allow token mapping file to not exist. Some tokens may be standardized or specific to the implementation, and may not need to be defined in YAML. --- .../apache/guacamole/vault/conf/VaultConfigurationService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java index b398c4919..25d1b9326 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.inject.Inject; import java.io.File; import java.io.IOException; +import java.util.Collections; import java.util.Map; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; @@ -91,6 +92,8 @@ public abstract class VaultConfigurationService { // Get configuration file from GUACAMOLE_HOME File confFile = new File(environment.getGuacamoleHome(), tokenMappingFilename); + if (!confFile.exists()) + return Collections.emptyMap(); // Deserialize token mapping from YAML try { From d0bd4b52d6b4e0b61fec20fce41c13740659e0b5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 19/36] GUACAMOLE-641: Add general service for retrieving data from Keeper records. --- .../ksm/KsmAuthenticationProviderModule.java | 2 + .../vault/ksm/secret/KsmRecordService.java | 124 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProviderModule.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProviderModule.java index df3adf8c6..bcc5a784e 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProviderModule.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/KsmAuthenticationProviderModule.java @@ -25,6 +25,7 @@ import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService; import org.apache.guacamole.vault.ksm.secret.KsmSecretService; import org.apache.guacamole.vault.conf.VaultConfigurationService; import org.apache.guacamole.vault.ksm.secret.KsmClient; +import org.apache.guacamole.vault.ksm.secret.KsmRecordService; import org.apache.guacamole.vault.secret.VaultSecretService; /** @@ -49,6 +50,7 @@ public class KsmAuthenticationProviderModule // Bind services specific to Keeper Secrets Manager bind(KsmClient.class); + bind(KsmRecordService.class); bind(VaultConfigurationService.class).to(KsmConfigurationService.class); bind(VaultSecretService.class).to(KsmSecretService.class); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java new file mode 100644 index 000000000..c71e9fced --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.vault.ksm.secret; + +import com.google.inject.Singleton; +import com.keepersecurity.secretsManager.core.KeeperRecord; +import com.keepersecurity.secretsManager.core.KeeperRecordData; +import com.keepersecurity.secretsManager.core.KeyPair; +import com.keepersecurity.secretsManager.core.KeyPairs; +import com.keepersecurity.secretsManager.core.Login; +import java.util.List; + +/** + * Service for automatically parsing out secrets and data from Keeper records. + */ +@Singleton +public class KsmRecordService { + + /** + * Returns the single username associated with the given record. If the + * record has no associated username, or multiple usernames, null is + * returned. Usernames are retrieved from "Login" fields. + * + * @param record + * The record to retrieve the username from. + * + * @return + * The username associated with the given record, or null if the record + * has no associated username or multiple usernames. + */ + public String getUsername(KeeperRecord record) { + + KeeperRecordData data = record.getData(); + + Login loginField = (Login) data.getField(Login.class); + if (loginField == null) + return null; + + List usernames = loginField.getValue(); + if (usernames.size() != 1) + return null; + + return usernames.get(0); + + } + + /** + * Returns the password associated with the given record, as dictated by + * the {@link KeeperRecord#getPassword()}. + * + * @param record + * The record to retrieve the password from. + * + * @return + * The password associated with the given record, or null if the record + * has no associated password. + */ + public String getPassword(KeeperRecord record) { + return record.getPassword(); + } + + /** + * Returns the private key associated with the given record. If the record + * has no associated private key, or multiple private keys, null is + * returned. Private keys are retrieved from "KeyPairs" fields. + * + * @param record + * The record to retrieve the private key from. + * + * @return + * The private key associated with the given record, or null if the + * record has no associated private key or multiple private keys. + */ + public String getPrivateKey(KeeperRecord record) { + + KeeperRecordData data = record.getData(); + + KeyPairs keyPairsField = (KeyPairs) data.getField(KeyPairs.class); + if (keyPairsField == null) + return null; + + List keyPairs = keyPairsField.getValue(); + if (keyPairs.size() != 1) + return null; + + return keyPairs.get(0).getPrivateKey(); + + } + + /** + * Returns the passphrase for the private key associated with the given + * record. Currently, this is simply dictated by {@link KeeperRecord#getPassword()}, + * as there is no specific association between private keys and passphrases + * in the "KeyPairs" field type. + * + * @param record + * The record to retrieve the passphrase from. + * + * @return + * The passphrase for the private key associated with the given record, + * or null if there is no such passphrase associated with the record. + */ + public String getPassphrase(KeeperRecord record) { + return getPassword(record); + } + +} From 30f24de808d59d1f6205a7a6beb0e06b78705bd9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 20/36] GUACAMOLE-641: Allow vault implementations to automatically provide tokens based on connection parameters (without YAML mapping). --- .../secret/AzureKeyVaultSecretService.java | 10 +++++ .../vault/secret/VaultSecretService.java | 27 ++++++++++++ .../vault/user/VaultUserContext.java | 42 ++++++++++++------- .../vault/ksm/secret/KsmSecretService.java | 10 +++++ 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java index cf9faa4c3..07337eb12 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java @@ -26,10 +26,14 @@ import com.microsoft.azure.keyvault.KeyVaultClient; import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; import com.microsoft.azure.keyvault.models.SecretBundle; import com.microsoft.rest.ServiceCallback; +import java.util.Collections; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.apache.guacamole.vault.azure.conf.AzureKeyVaultAuthenticationException; import org.apache.guacamole.vault.azure.conf.AzureKeyVaultConfigurationService; import org.apache.guacamole.vault.secret.CachedVaultSecretService; @@ -118,4 +122,10 @@ public class AzureKeyVaultSecretService extends CachedVaultSecretService { } + @Override + public Map> getTokens(GuacamoleConfiguration config) + throws GuacamoleException { + return Collections.emptyMap(); + } + } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java index 1ff230ca5..4cc5bb4a8 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java @@ -19,8 +19,10 @@ package org.apache.guacamole.vault.secret; +import java.util.Map; import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.protocol.GuacamoleConfiguration; /** * Generic service for retrieving the value of a secret stored in a vault. @@ -69,4 +71,29 @@ public interface VaultSecretService { */ Future getValue(String name) throws GuacamoleException; + /** + * Returns a map of token names to corresponding Futures which eventually + * complete with the value of that token, where each token is dynamically + * defined based on connection parameters. If a vault implementation allows + * for predictable secrets based on the parameters of a connection, this + * function should be implemented to provide automatic tokens for those + * secrets and remove the need for manual mapping via YAML. + * + * @param config + * The configuration of the Guacamole connection for which tokens are + * being generated. This configuration may be empty or partial, + * depending on the underlying implementation. + * + * @return + * A map of token names to their corresponding future values, where + * each token and value may be dynamically determined based on the + * connection configuration. + * + * @throws GuacamoleException + * If an error occurs producing the tokens and values required for the + * given configuration. + */ + Map> getTokens(GuacamoleConfiguration config) + throws GuacamoleException; + } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java index 1367ac438..6df30fdad 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java @@ -32,6 +32,7 @@ import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.TokenInjectingUserContext; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.apache.guacamole.token.GuacamoleTokenUndefinedException; import org.apache.guacamole.token.TokenFilter; import org.apache.guacamole.vault.conf.VaultConfigurationService; @@ -188,6 +189,10 @@ public class VaultUserContext extends TokenInjectingUserContext { * may contain its own tokens, which will be substituted using values * from the given filter. * + * @param config + * The GuacamoleConfiguration of the connection for which tokens are + * being retrieved, if available. This may be null. + * * @param filter * The filter to use to substitute values for tokens in the names of * secrets to be retrieved from the vault. @@ -202,7 +207,8 @@ public class VaultUserContext extends TokenInjectingUserContext { * vault due to an error. */ private Map> getTokens(Map tokenMapping, - TokenFilter filter) throws GuacamoleException { + GuacamoleConfiguration config, TokenFilter filter) + throws GuacamoleException { // Populate map with pending secret retrieval operations corresponding // to each mapped token @@ -230,6 +236,9 @@ public class VaultUserContext extends TokenInjectingUserContext { } + // Additionally include any dynamic, parameter-based tokens + pendingTokens.putAll(secretService.getTokens(config)); + return pendingTokens; } @@ -304,28 +313,28 @@ public class VaultUserContext extends TokenInjectingUserContext { // Substitute tokens producing secret names, retrieving and storing // those secrets as parameter tokens - return resolve(getTokens(confService.getTokenMapping(), filter)); + return resolve(getTokens(confService.getTokenMapping(), null, filter)); } /** - * Retrieves the connection parameters associated with the - * GuacamoleConfiguration of the given Connection. If possible, privileged - * access to those parameters is obtained first. Note that the underlying - * extension is not required to allow privileged access, nor is it - * required to expose the underlying connection parameters at all. + * Retrieves the GuacamoleConfiguration of the given Connection. If + * possible, privileged access to the configuration is obtained first. Note + * that the underlying extension is not required to allow privileged + * access, nor is it required to expose the underlying configuration at + * all. * * @param connection - * The connection to retrieve parameters from. + * The connection to retrieve the configuration from. * * @return - * A Map of all connection parameters exposed by the underlying - * extension for the given connection, which may be empty. + * The GuacamoleConfiguration associated with the given connection, + * which may be partial or empty. * * @throws GuacamoleException - * If an error prevents privileged retrieval of parameters. + * If an error prevents privileged retrieval of the configuration. */ - private Map getConnectionParameters(Connection connection) + private GuacamoleConfiguration getConnectionConfiguration(Connection connection) throws GuacamoleException { String identifier = connection.getIdentifier(); @@ -335,11 +344,11 @@ public class VaultUserContext extends TokenInjectingUserContext { // actually be privileged) Connection privilegedConnection = getPrivileged().getConnectionDirectory().get(identifier); if (privilegedConnection != null) - return privilegedConnection.getConfiguration().getParameters(); + return privilegedConnection.getConfiguration(); // Fall back to unprivileged access if not implemented/allowed by // extension - return connection.getConfiguration().getParameters(); + return connection.getConfiguration(); } @@ -360,7 +369,8 @@ public class VaultUserContext extends TokenInjectingUserContext { // Add hostname and username tokens if available (implementations are // not required to expose connection configuration details) - Map parameters = getConnectionParameters(connection); + GuacamoleConfiguration config = getConnectionConfiguration(connection); + Map parameters = config.getParameters(); String hostname = parameters.get("hostname"); if (hostname != null && !hostname.isEmpty()) @@ -382,7 +392,7 @@ public class VaultUserContext extends TokenInjectingUserContext { // Substitute tokens producing secret names, retrieving and storing // those secrets as parameter tokens - return resolve(getTokens(confService.getTokenMapping(), filter)); + return resolve(getTokens(confService.getTokenMapping(), config, filter)); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java index c8ae0d7e3..42baff445 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java @@ -23,9 +23,12 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.util.Collections; +import java.util.Map; import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.apache.guacamole.vault.secret.VaultSecretService; /** @@ -59,4 +62,11 @@ public class KsmSecretService implements VaultSecretService { return ksm.getSecret(name); } + @Override + public Map> getTokens(GuacamoleConfiguration config) + throws GuacamoleException { + // STUB + return Collections.emptyMap(); + } + } From d2f55960152396c34d9122e5bcdb691b5ad4a82c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 21/36] GUACAMOLE-641: Automatically provide KEEPER_SERVER_* tokens based on connection parameters. --- .../vault/ksm/secret/KsmSecretService.java | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java index 42baff445..e1c3137ce 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java @@ -21,10 +21,12 @@ package org.apache.guacamole.vault.ksm.secret; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.keepersecurity.secretsManager.core.KeeperRecord; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; @@ -43,6 +45,12 @@ public class KsmSecretService implements VaultSecretService { @Inject private KsmClient ksm; + /** + * Service for retrieving data from records. + */ + @Inject + private KsmRecordService recordService; + @Override public String canonicalize(String nameComponent) { try { @@ -65,8 +73,48 @@ public class KsmSecretService implements VaultSecretService { @Override public Map> getTokens(GuacamoleConfiguration config) throws GuacamoleException { - // STUB - return Collections.emptyMap(); + + Map> tokens = new HashMap<>(); + + // TODO: Ensure tokens within parameters are evaluated when considering + // whether a KSM record matches (ie: "username" might be ${GUAC_USERNAME}) + + // TODO: Verify protocol before assuming meaning of "hostname" + // parameter + + Map parameters = config.getParameters(); + + // Retrieve and define server-specific tokens, if any + String hostname = parameters.get("hostname"); + if (hostname != null && !hostname.isEmpty()) { + KeeperRecord record = ksm.getRecordByHost(hostname); + if (record != null) { + + // Username of server-related record + String username = recordService.getUsername(record); + if (username != null) + tokens.put("KEEPER_SERVER_USERNAME", CompletableFuture.completedFuture(username)); + + // Password of server-related record + String password = recordService.getPassword(record); + if (password != null) + tokens.put("KEEPER_SERVER_PASSWORD", CompletableFuture.completedFuture(password)); + + // Key passphrase of server-related record + String passphrase = recordService.getPassphrase(record); + if (passphrase != null) + tokens.put("KEEPER_SERVER_PASSPHRASE", CompletableFuture.completedFuture(passphrase)); + + // Private key of server-related record + String privateKey = recordService.getPrivateKey(record); + if (privateKey != null) + tokens.put("KEEPER_SERVER_KEY", CompletableFuture.completedFuture(privateKey)); + + } + } + + return tokens; + } } From 7641fa922204d841999517d7cf1191a4f6398fca Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 22/36] GUACAMOLE-641: Allow TokenInjecting* implementations to consider values of existing tokens. --- .../net/auth/TokenInjectingConnection.java | 28 ++++++-- .../auth/TokenInjectingConnectionGroup.java | 34 +++++++-- .../net/auth/TokenInjectingUserContext.java | 72 +++++++++++++++---- 3 files changed, 111 insertions(+), 23 deletions(-) diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java index 51ca6e33e..422e0ea34 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java @@ -29,7 +29,9 @@ import org.apache.guacamole.protocol.GuacamoleClientInformation; /** * Connection implementation which overrides the connect() function of an * underlying Connection, adding a given set of parameter tokens to the tokens - * already supplied. + * already supplied. If not supplying a static set of tokens at construction + * time, implementations should override either {@link #addTokens(java.util.Map)} + * or {@link #getTokens()} to provide tokens dynamically. */ public class TokenInjectingConnection extends DelegatingConnection { @@ -41,7 +43,9 @@ public class TokenInjectingConnection extends DelegatingConnection { /** * Returns the tokens which should be added to an in-progress call to * connect(). If not overridden, this function will return the tokens - * provided when this instance of TokenInjectingConnection was created. + * provided when this instance of TokenInjectingConnection was created. If + * the values of existing tokens need to be considered, implementations + * should override {@link #addTokens(java.util.Map)} instead. * * @return * The tokens which should be added to the in-progress call to @@ -54,6 +58,21 @@ public class TokenInjectingConnection extends DelegatingConnection { return tokens; } + /** + * Adds tokens to an in-progress call to connect(). If not overridden, this + * function will add the tokens returned by {@link #getTokens()}. + * + * @param tokens + * A modifiable Map containing the tokens already supplied to + * connect(). + * + * @throws GuacamoleException + * If the applicable tokens cannot be generated. + */ + protected void addTokens(Map tokens) throws GuacamoleException { + tokens.putAll(getTokens()); + } + /** * Wraps the given Connection, automatically adding the given tokens to * each invocation of connect(). Any additional tokens which have the same @@ -73,7 +92,8 @@ public class TokenInjectingConnection extends DelegatingConnection { /** * Wraps the given Connection such that the additional parameter tokens - * returned by getTokens() are included with each invocation of connect(). + * added by {@link #addTokens(java.util.Map)} or returned by + * {@link #getTokens()} are included with each invocation of connect(). * Any additional tokens which have the same name as existing tokens will * override the existing values. * @@ -90,7 +110,7 @@ public class TokenInjectingConnection extends DelegatingConnection { // Apply provided tokens over those given to connect() tokens = new HashMap<>(tokens); - tokens.putAll(getTokens()); + addTokens(tokens); return super.connect(info, tokens); diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java index 62f71630a..c7dfa0a42 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java @@ -29,7 +29,10 @@ import org.apache.guacamole.protocol.GuacamoleClientInformation; /** * ConnectionGroup implementation which overrides the connect() function of an * underlying ConnectionGroup, adding a given set of parameter tokens to the - * tokens already supplied. + * tokens already supplied. If not supplying a static set of tokens at + * construction time, implementations should override either + * {@link #addTokens(java.util.Map)} or {@link #getTokens()} to provide tokens + * dynamically. */ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup { @@ -41,8 +44,9 @@ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup { /** * Returns the tokens which should be added to an in-progress call to * connect(). If not overridden, this function will return the tokens - * provided when this instance of TokenInjectingConnectionGroup was - * created. + * provided when this instance of TokenInjectingConnection was created. If + * the values of existing tokens need to be considered, implementations + * should override {@link #addTokens(java.util.Map)} instead. * * @return * The tokens which should be added to the in-progress call to @@ -55,6 +59,21 @@ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup { return tokens; } + /** + * Adds tokens to an in-progress call to connect(). If not overridden, this + * function will add the tokens returned by {@link #getTokens()}. + * + * @param tokens + * A modifiable Map containing the tokens already supplied to + * connect(). + * + * @throws GuacamoleException + * If the applicable tokens cannot be generated. + */ + protected void addTokens(Map tokens) throws GuacamoleException { + tokens.putAll(getTokens()); + } + /** * Wraps the given ConnectionGroup, automatically adding the given tokens * to each invocation of connect(). Any additional tokens which have the @@ -74,9 +93,10 @@ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup { /** * Wraps the given ConnectionGroup such that the additional parameter - * tokens returned by getTokens() are included with each invocation of - * connect(). Any additional tokens which have the same name as existing - * tokens will override the existing values. + * tokens added by {@link #addTokens(java.util.Map)} or returned by + * {@link #getTokens()} are included with each invocation of connect(). Any + * additional tokens which have the same name as existing tokens will + * override the existing values. * * @param connectionGroup * The ConnectionGroup to wrap. @@ -91,7 +111,7 @@ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup { // Apply provided tokens over those given to connect() tokens = new HashMap<>(tokens); - tokens.putAll(getTokens()); + addTokens(tokens); return super.connect(info, tokens); diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java index e4940e5d8..79c2769b2 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java @@ -32,7 +32,7 @@ public class TokenInjectingUserContext extends DelegatingUserContext { /** * The additional tokens to include with each call to connect() if - * getTokens() is not overridden. + * getTokens() or addTokens() are not overridden. */ private final Map tokens; @@ -42,8 +42,8 @@ public class TokenInjectingUserContext extends DelegatingUserContext { * parameter tokens are included. Any additional tokens which have the same * name as existing tokens will override the existing values. If tokens * specific to a particular connection or connection group need to be - * included, getTokens() may be overridden to provide a different set of - * tokens. + * included, getTokens() or addTokens() may be overridden to provide a + * different set of tokens. * * @param userContext * The UserContext to wrap. @@ -60,9 +60,9 @@ public class TokenInjectingUserContext extends DelegatingUserContext { /** * Wraps the given UserContext, overriding the connect() function of each * retrieved Connection and ConnectionGroup such that the additional - * parameter tokens returned by getTokens() are included. Any additional - * tokens which have the same name as existing tokens will override the - * existing values. + * parameter tokens added by addTokens() or returned by getTokens() are + * included. Any additional tokens which have the same name as existing + * tokens will override the existing values. * * @param userContext * The UserContext to wrap. @@ -75,7 +75,10 @@ public class TokenInjectingUserContext extends DelegatingUserContext { * Returns the tokens which should be added to an in-progress call to * connect() for the given Connection. If not overridden, this function * will return the tokens provided when this instance of - * TokenInjectingUserContext was created. + * TokenInjectingUserContext was created. If the values of existing tokens + * need to be considered, implementations should override + * {@link #addTokens(org.apache.guacamole.net.auth.Connection, java.util.Map)} + * instead. * * @param connection * The Connection on which connect() has been called. @@ -93,11 +96,35 @@ public class TokenInjectingUserContext extends DelegatingUserContext { return tokens; } + /** + * Adds tokens to an in-progress call to connect() for the given + * Connection. If not overridden, this function will add the tokens + * returned by {@link #getTokens(org.apache.guacamole.net.auth.Connection)}. + * + * @param connection + * The Connection on which connect() has been called. + * + * @param tokens + * A modifiable Map containing the tokens already supplied to + * connect(). + * + * @throws GuacamoleException + * If the tokens applicable to the given connection cannot be + * generated. + */ + protected void addTokens(Connection connection, Map tokens) + throws GuacamoleException { + tokens.putAll(getTokens(connection)); + } + /** * Returns the tokens which should be added to an in-progress call to * connect() for the given ConnectionGroup. If not overridden, this * function will return the tokens provided when this instance of - * TokenInjectingUserContext was created. + * TokenInjectingUserContext was created. If the values of existing tokens + * need to be considered, implementations should override + * {@link #addTokens(org.apache.guacamole.net.auth.ConnectionGroup, java.util.Map)} + * instead. * * @param connectionGroup * The ConnectionGroup on which connect() has been called. @@ -115,6 +142,27 @@ public class TokenInjectingUserContext extends DelegatingUserContext { return tokens; } + /** + * Adds tokens to an in-progress call to connect() for the given + * ConnectionGroup. If not overridden, this function will add the tokens + * returned by {@link #getTokens(org.apache.guacamole.net.auth.ConnectionGroup)}. + * + * @param connectionGroup + * The ConnectionGroup on which connect() has been called. + * + * @param tokens + * A modifiable Map containing the tokens already supplied to + * connect(). + * + * @throws GuacamoleException + * If the tokens applicable to the given connection cannot be + * generated. + */ + protected void addTokens(ConnectionGroup connectionGroup, + Map tokens) throws GuacamoleException { + tokens.putAll(getTokens(connectionGroup)); + } + @Override public Directory getConnectionGroupDirectory() throws GuacamoleException { @@ -125,8 +173,8 @@ public class TokenInjectingUserContext extends DelegatingUserContext { return new TokenInjectingConnectionGroup(object) { @Override - protected Map getTokens() throws GuacamoleException { - return TokenInjectingUserContext.this.getTokens(object); + protected void addTokens(Map tokens) throws GuacamoleException { + TokenInjectingUserContext.this.addTokens(object, tokens); } }; @@ -150,8 +198,8 @@ public class TokenInjectingUserContext extends DelegatingUserContext { return new TokenInjectingConnection(object) { @Override - protected Map getTokens() throws GuacamoleException { - return TokenInjectingUserContext.this.getTokens(object); + protected void addTokens(Map tokens) throws GuacamoleException { + TokenInjectingUserContext.this.addTokens(object, tokens); } }; From b65586605760bc77429502ff4946949376d5b990 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 23/36] GUACAMOLE-641: Consider existing tokens when injecting tokens from vault. --- .../secret/AzureKeyVaultSecretService.java | 5 +-- .../vault/secret/VaultSecretService.java | 10 ++++-- .../vault/user/VaultUserContext.java | 31 ++++++++++++------- .../vault/ksm/secret/KsmSecretService.java | 10 +++--- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java index 07337eb12..401a44607 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java @@ -34,6 +34,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.protocol.GuacamoleConfiguration; +import org.apache.guacamole.token.TokenFilter; import org.apache.guacamole.vault.azure.conf.AzureKeyVaultAuthenticationException; import org.apache.guacamole.vault.azure.conf.AzureKeyVaultConfigurationService; import org.apache.guacamole.vault.secret.CachedVaultSecretService; @@ -123,8 +124,8 @@ public class AzureKeyVaultSecretService extends CachedVaultSecretService { } @Override - public Map> getTokens(GuacamoleConfiguration config) - throws GuacamoleException { + public Map> getTokens(GuacamoleConfiguration config, + TokenFilter filter) throws GuacamoleException { return Collections.emptyMap(); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java index 4cc5bb4a8..76349bad9 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/secret/VaultSecretService.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.protocol.GuacamoleConfiguration; +import org.apache.guacamole.token.TokenFilter; /** * Generic service for retrieving the value of a secret stored in a vault. @@ -84,6 +85,11 @@ public interface VaultSecretService { * being generated. This configuration may be empty or partial, * depending on the underlying implementation. * + * @param filter + * A TokenFilter instance that applies any tokens already available to + * be applied to the configuration of the Guacamole connection. These + * tokens will consist of tokens already supplied to connect(). + * * @return * A map of token names to their corresponding future values, where * each token and value may be dynamically determined based on the @@ -93,7 +99,7 @@ public interface VaultSecretService { * If an error occurs producing the tokens and values required for the * given configuration. */ - Map> getTokens(GuacamoleConfiguration config) - throws GuacamoleException; + Map> getTokens(GuacamoleConfiguration config, + TokenFilter filter) throws GuacamoleException; } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java index 6df30fdad..53901483e 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/user/VaultUserContext.java @@ -189,13 +189,18 @@ public class VaultUserContext extends TokenInjectingUserContext { * may contain its own tokens, which will be substituted using values * from the given filter. * + * @param secretNameFilter + * The filter to use to substitute values for tokens in the names of + * secrets to be retrieved from the vault. + * * @param config * The GuacamoleConfiguration of the connection for which tokens are * being retrieved, if available. This may be null. * - * @param filter - * The filter to use to substitute values for tokens in the names of - * secrets to be retrieved from the vault. + * @param configFilter + * A TokenFilter instance that applies any tokens already available to + * be applied to the configuration of the Guacamole connection. These + * tokens will consist of tokens already supplied to connect(). * * @return * A Map of token name to Future, where each Future represents the @@ -207,8 +212,8 @@ public class VaultUserContext extends TokenInjectingUserContext { * vault due to an error. */ private Map> getTokens(Map tokenMapping, - GuacamoleConfiguration config, TokenFilter filter) - throws GuacamoleException { + TokenFilter secretNameFilter, GuacamoleConfiguration config, + TokenFilter configFilter) throws GuacamoleException { // Populate map with pending secret retrieval operations corresponding // to each mapped token @@ -219,7 +224,7 @@ public class VaultUserContext extends TokenInjectingUserContext { // secrets which cannot be translated String secretName; try { - secretName = filter.filterStrict(entry.getValue()); + secretName = secretNameFilter.filterStrict(entry.getValue()); } catch (GuacamoleTokenUndefinedException e) { logger.debug("Secret for token \"{}\" will not be retrieved. " @@ -237,7 +242,7 @@ public class VaultUserContext extends TokenInjectingUserContext { } // Additionally include any dynamic, parameter-based tokens - pendingTokens.putAll(secretService.getTokens(config)); + pendingTokens.putAll(secretService.getTokens(config, configFilter)); return pendingTokens; @@ -298,8 +303,8 @@ public class VaultUserContext extends TokenInjectingUserContext { } @Override - protected Map getTokens(ConnectionGroup connectionGroup) - throws GuacamoleException { + protected void addTokens(ConnectionGroup connectionGroup, + Map tokens) throws GuacamoleException { String name = connectionGroup.getName(); String identifier = connectionGroup.getIdentifier(); @@ -313,7 +318,8 @@ public class VaultUserContext extends TokenInjectingUserContext { // Substitute tokens producing secret names, retrieving and storing // those secrets as parameter tokens - return resolve(getTokens(confService.getTokenMapping(), null, filter)); + tokens.putAll(resolve(getTokens(confService.getTokenMapping(), filter, + null, new TokenFilter(tokens)))); } @@ -353,7 +359,7 @@ public class VaultUserContext extends TokenInjectingUserContext { } @Override - protected Map getTokens(Connection connection) + protected void addTokens(Connection connection, Map tokens) throws GuacamoleException { String name = connection.getName(); @@ -392,7 +398,8 @@ public class VaultUserContext extends TokenInjectingUserContext { // Substitute tokens producing secret names, retrieving and storing // those secrets as parameter tokens - return resolve(getTokens(confService.getTokenMapping(), config, filter)); + tokens.putAll(resolve(getTokens(confService.getTokenMapping(), filter, + config, new TokenFilter(tokens)))); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java index e1c3137ce..6fd9f203c 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java @@ -31,6 +31,7 @@ import java.util.concurrent.Future; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.protocol.GuacamoleConfiguration; +import org.apache.guacamole.token.TokenFilter; import org.apache.guacamole.vault.secret.VaultSecretService; /** @@ -71,14 +72,11 @@ public class KsmSecretService implements VaultSecretService { } @Override - public Map> getTokens(GuacamoleConfiguration config) - throws GuacamoleException { + public Map> getTokens(GuacamoleConfiguration config, + TokenFilter filter) throws GuacamoleException { Map> tokens = new HashMap<>(); - // TODO: Ensure tokens within parameters are evaluated when considering - // whether a KSM record matches (ie: "username" might be ${GUAC_USERNAME}) - // TODO: Verify protocol before assuming meaning of "hostname" // parameter @@ -87,7 +85,7 @@ public class KsmSecretService implements VaultSecretService { // Retrieve and define server-specific tokens, if any String hostname = parameters.get("hostname"); if (hostname != null && !hostname.isEmpty()) { - KeeperRecord record = ksm.getRecordByHost(hostname); + KeeperRecord record = ksm.getRecordByHost(filter.filter(hostname)); if (record != null) { // Username of server-related record From c5ae02722522de415fbcd41e8f133bb7c57d047e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 24/36] GUACAMOLE-641: Add user- and gateway-specific tokens. --- .../vault/ksm/secret/KsmSecretService.java | 90 +++++++++++++------ 1 file changed, 65 insertions(+), 25 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java index 6fd9f203c..d7b4deb50 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java @@ -71,44 +71,84 @@ public class KsmSecretService implements VaultSecretService { return ksm.getSecret(name); } + /** + * Adds contextual parameter tokens for the secrets in the given record to + * the given map of existing tokens. The values of each token are + * determined from secrets within the record. Depending on the record, this + * will be a subset of the username, password, private key, and passphrase. + * + * @param tokens + * The map of parameter tokens that any new tokens should be added to. + * + * @param prefix + * The prefix that should be prepended to each added token. + * + * @param record + * The record to retrieve secrets from when generating tokens. This may + * be null. + */ + private void addRecordTokens(Map> tokens, String prefix, + KeeperRecord record) { + + if (record == null) + return; + + // Username of server-related record + String username = recordService.getUsername(record); + if (username != null) + tokens.put(prefix + "USERNAME", CompletableFuture.completedFuture(username)); + + // Password of server-related record + String password = recordService.getPassword(record); + if (password != null) + tokens.put(prefix + "PASSWORD", CompletableFuture.completedFuture(password)); + + // Key passphrase of server-related record + String passphrase = recordService.getPassphrase(record); + if (passphrase != null) + tokens.put(prefix + "PASSPHRASE", CompletableFuture.completedFuture(passphrase)); + + // Private key of server-related record + String privateKey = recordService.getPrivateKey(record); + if (privateKey != null) + tokens.put(prefix + "KEY", CompletableFuture.completedFuture(privateKey)); + + } + @Override public Map> getTokens(GuacamoleConfiguration config, TokenFilter filter) throws GuacamoleException { Map> tokens = new HashMap<>(); - - // TODO: Verify protocol before assuming meaning of "hostname" - // parameter - Map parameters = config.getParameters(); // Retrieve and define server-specific tokens, if any String hostname = parameters.get("hostname"); - if (hostname != null && !hostname.isEmpty()) { - KeeperRecord record = ksm.getRecordByHost(filter.filter(hostname)); - if (record != null) { + if (hostname != null && !hostname.isEmpty()) + addRecordTokens(tokens, "KEEPER_SERVER_", + ksm.getRecordByHost(filter.filter(hostname))); - // Username of server-related record - String username = recordService.getUsername(record); - if (username != null) - tokens.put("KEEPER_SERVER_USERNAME", CompletableFuture.completedFuture(username)); + // Retrieve and define user-specific tokens, if any + String username = parameters.get("username"); + if (username != null && !username.isEmpty()) + addRecordTokens(tokens, "KEEPER_USER_", + ksm.getRecordByLogin(filter.filter(username))); - // Password of server-related record - String password = recordService.getPassword(record); - if (password != null) - tokens.put("KEEPER_SERVER_PASSWORD", CompletableFuture.completedFuture(password)); + // Tokens specific to RDP + if ("rdp".equals(config.getProtocol())) { + + // Retrieve and define gateway server-specific tokens, if any + String gatewayHostname = parameters.get("gateway-hostname"); + if (gatewayHostname != null && !gatewayHostname.isEmpty()) + addRecordTokens(tokens, "KEEPER_GATEWAY_", + ksm.getRecordByHost(filter.filter(gatewayHostname))); - // Key passphrase of server-related record - String passphrase = recordService.getPassphrase(record); - if (passphrase != null) - tokens.put("KEEPER_SERVER_PASSPHRASE", CompletableFuture.completedFuture(passphrase)); + // Retrieve and define gateway user-specific tokens, if any + String gatewayUsername = parameters.get("gateway-username"); + if (gatewayUsername != null && !gatewayUsername.isEmpty()) + addRecordTokens(tokens, "KEEPER_GATEWAY_USER_", + ksm.getRecordByLogin(filter.filter(gatewayUsername))); - // Private key of server-related record - String privateKey = recordService.getPrivateKey(record); - if (privateKey != null) - tokens.put("KEEPER_SERVER_KEY", CompletableFuture.completedFuture(privateKey)); - - } } return tokens; From 62863f8a0b246191adda19ea2fa3ad2cae188390 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 25/36] GUACAMOLE-641: Log possible ambiguous record retrievals at debug level. --- .../apache/guacamole/vault/ksm/secret/KsmClient.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java index a72a2fdaa..3969e18c6 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java @@ -387,8 +387,12 @@ public class KsmClient { cacheLock.readLock().lock(); try { - if (cachedAmbiguousHosts.contains(hostname)) + if (cachedAmbiguousHosts.contains(hostname)) { + logger.debug("The hostname/address \"{}\" is referenced by " + + "multiple Keeper records and cannot be used to " + + "locate individual secrets.", hostname); return null; + } return cachedRecordsByHost.get(hostname); @@ -417,8 +421,12 @@ public class KsmClient { cacheLock.readLock().lock(); try { - if (cachedAmbiguousUsernames.contains(username)) + if (cachedAmbiguousUsernames.contains(username)) { + logger.debug("The username \"{}\" is referenced by multiple " + + "Keeper records and cannot be used to locate " + + "individual secrets.", username); return null; + } return cachedRecordsByUsername.get(username); From aee1b13b2b9cc4d627b168daa3cdd792ec311b2e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 26/36] GUACAMOLE-641: Include KSM extension in vault distribution. --- .../guacamole-vault/modules/guacamole-vault-dist/pom.xml | 7 +++++++ .../guacamole-vault-dist/src/main/assembly/dist.xml | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml b/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml index abbba239c..a5042dfb8 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml @@ -49,6 +49,13 @@ 1.4.0 + + + org.apache.guacamole + guacamole-vault-ksm + 1.4.0 + + diff --git a/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml b/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml index 8d5c4b866..30c93f41a 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml @@ -41,6 +41,14 @@ + + + ksm + + org.apache.guacamole:guacamole-vault-ksm + + + From b6e6800c0d31d49e100533e14f6012b6161f1d8d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 27/36] GUACAMOLE-641: Consider null token mapping (blank YAML) to be empty. --- .../guacamole/vault/conf/VaultConfigurationService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java index 25d1b9326..36a74ea4c 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java @@ -97,7 +97,13 @@ public abstract class VaultConfigurationService { // Deserialize token mapping from YAML try { - return mapper.readValue(confFile, new TypeReference>() {}); + + Map mapping = mapper.readValue(confFile, new TypeReference>() {}); + if (mapping == null) + return Collections.emptyMap(); + + return mapping; + } // Fail if YAML is invalid/unreadable From f8f0779d7a2f930a798336883d51326d548842dd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 28/36] GUACAMOLE-641: Manually extract password value from KeeperRecord. Simply calling getPassword() does not currently work correctly, as the implementation of getPassword() assumes there will be at least one value if the field is present. This results in an ArrayIndexOutOfBoundsException for records with empty passwords: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:659) at java.util.ArrayList.get(ArrayList.java:435) at com.keepersecurity.secretsManager.core.KeeperRecord.getPassword(SecretsManager.kt:134) ... --- .../vault/ksm/secret/KsmRecordService.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java index c71e9fced..ac0371a91 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java @@ -25,6 +25,7 @@ import com.keepersecurity.secretsManager.core.KeeperRecordData; import com.keepersecurity.secretsManager.core.KeyPair; import com.keepersecurity.secretsManager.core.KeyPairs; import com.keepersecurity.secretsManager.core.Login; +import com.keepersecurity.secretsManager.core.Password; import java.util.List; /** @@ -73,7 +74,19 @@ public class KsmRecordService { * has no associated password. */ public String getPassword(KeeperRecord record) { - return record.getPassword(); + + KeeperRecordData data = record.getData(); + + Password passwordField = (Password) data.getField(Password.class); + if (passwordField == null) + return null; + + List values = passwordField.getValue(); + if (values.size() != 1) + return null; + + return values.get(0); + } /** From 55b7e6f86789ff26de0fe439e6d82f8ccb6bb4e4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 29/36] GUACAMOLE-641: Additionally match against KSM custom fields based on labels. --- .../vault/ksm/secret/KsmRecordService.java | 298 +++++++++++++++--- 1 file changed, 261 insertions(+), 37 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java index ac0371a91..5eef05ffd 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java @@ -20,13 +20,19 @@ package org.apache.guacamole.vault.ksm.secret; import com.google.inject.Singleton; +import com.keepersecurity.secretsManager.core.HiddenField; import com.keepersecurity.secretsManager.core.KeeperRecord; import com.keepersecurity.secretsManager.core.KeeperRecordData; +import com.keepersecurity.secretsManager.core.KeeperRecordField; import com.keepersecurity.secretsManager.core.KeyPair; import com.keepersecurity.secretsManager.core.KeyPairs; import com.keepersecurity.secretsManager.core.Login; import com.keepersecurity.secretsManager.core.Password; +import com.keepersecurity.secretsManager.core.Text; import java.util.List; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Service for automatically parsing out secrets and data from Keeper records. @@ -34,10 +40,197 @@ import java.util.List; @Singleton public class KsmRecordService { + /** + * Regular expression which matches the labels of custom fields containing + * usernames. + */ + private static final Pattern USERNAME_LABEL_PATTERN = + Pattern.compile("username", Pattern.CASE_INSENSITIVE); + + /** + * Regular expression which matches the labels of custom fields containing + * passwords. + */ + private static final Pattern PASSWORD_LABEL_PATTERN = + Pattern.compile("password", Pattern.CASE_INSENSITIVE); + + /** + * Regular expression which matches the labels of custom fields containing + * passphrases for private keys. + */ + private static final Pattern PASSPHRASE_LABEL_PATTERN = + Pattern.compile("passphrase", Pattern.CASE_INSENSITIVE); + + /** + * Regular expression which matches the labels of custom fields containing + * private keys. + */ + private static final Pattern PRIVATE_KEY_LABEL_PATTERN = + Pattern.compile("private\\s*key", Pattern.CASE_INSENSITIVE); + + /** + * Returns the single value stored in the given list. If the list is empty + * or contains multiple values, null is returned. + * + * @param + * The type of object stored in the list. + * + * @param values + * The list to retrieve a single value from. + * + * @return + * The single value stored in the given list, or null if the list is + * empty or contains multiple values. + */ + private T getSingleValue(List values) { + + if (values == null || values.size() != 1) + return null; + + return values.get(0); + + } + + /** + * Returns the single value stored in the given list, additionally + * performing a mapping transformation on the single value. If the list is + * empty or contains multiple values, null is returned. + * + * @param + * The type of object stored in the list. + * + * @param + * The type of object to return. + * + * @param values + * The list to retrieve a single value from. + * + * @param mapper + * The function to use to map the single object of type T to type R. + * + * @return + * The single value stored in the given list, transformed using the + * provided mapping function, or null if the list is empty or contains + * multiple values. + */ + private R getSingleValue(List values, Function mapper) { + + T value = getSingleValue(values); + if (value == null) + return null; + + return mapper.apply(value); + + } + + /** + * Returns the instance of the only field that has the given type and + * matches the given label pattern. If there are no such fields, or + * multiple such fields, null is returned. + * + * @param + * The type of field to return. + * + * @param fields + * The list of fields to retrieve the field from. + * + * @param fieldClass + * The class representing the type of field to return. + * + * @param labelPattern + * The pattern to match against the desired field's label, or null if + * no label pattern match should be performed. + * + * @return + * The field having the given type and matching the given label + * pattern, or null if there is not exactly one such field. + */ + @SuppressWarnings("unchecked") // Manually verified with isAssignableFrom() + private T getField(List fields, + Class fieldClass, Pattern labelPattern) { + + T foundField = null; + for (KeeperRecordField field : fields) { + + // Ignore fields of wrong class + if (!fieldClass.isAssignableFrom(field.getClass())) + continue; + + // Match against provided pattern, if any + if (labelPattern != null) { + + // Ignore fields without labels if a label match is requested + String label = field.getLabel(); + if (label == null) + continue; + + // Ignore fields whose labels do not match + Matcher labelMatcher = labelPattern.matcher(label); + if (!labelMatcher.matches()) + continue; + + } + + // Ignore ambiguous fields + if (foundField != null) + return null; + + // Tentative match found - we can use this as long as no other + // field matches the criteria + foundField = (T) field; + + } + + return foundField; + + } + + /** + * Returns the instance of the only field that has the given type and + * matches the given label pattern. If there are no such fields, or + * multiple such fields, null is returned. Both standard and custom fields + * are searched. As standard fields do not have labels, any given label + * pattern is ignored for standard fields. + * + * @param + * The type of field to return. + * + * @param record + * The Keeper record to retrieve the field from. + * + * @param fieldClass + * The class representing the type of field to return. + * + * @param labelPattern + * The pattern to match against the labels of custom fields, or null if + * no label pattern match should be performed. + * + * @return + * The field having the given type and matching the given label + * pattern, or null if there is not exactly one such field. + */ + private T getField(KeeperRecord record, + Class fieldClass, Pattern labelPattern) { + + KeeperRecordData data = record.getData(); + + // Attempt to find standard field first, ignoring custom fields if a + // standard field exists (NOTE: standard fields do not have labels) + T field = getField(data.getFields(), fieldClass, null); + if (field != null) + return field; + + // Fall back on custom fields + return getField(data.getCustom(), fieldClass, labelPattern); + + } + /** * Returns the single username associated with the given record. If the * record has no associated username, or multiple usernames, null is - * returned. Usernames are retrieved from "Login" fields. + * returned. Usernames are retrieved from "Login" fields, as well as + * "Text" and "Hidden" fields that have the label "username" + * (case-insensitive). * * @param record * The record to retrieve the username from. @@ -48,23 +241,65 @@ public class KsmRecordService { */ public String getUsername(KeeperRecord record) { + // Prefer standard login field + Login loginField = getField(record, Login.class, null); + if (loginField != null) + return getSingleValue(loginField.getValue()); + KeeperRecordData data = record.getData(); + List custom = data.getCustom(); - Login loginField = (Login) data.getField(Login.class); - if (loginField == null) - return null; + // Use text "username" custom field as fallback ... + Text textField = getField(custom, Text.class, USERNAME_LABEL_PATTERN); + if (textField != null) + return getSingleValue(textField.getValue()); - List usernames = loginField.getValue(); - if (usernames.size() != 1) - return null; + // ... or hidden "username" custom field + HiddenField hiddenField = getField(custom, HiddenField.class, USERNAME_LABEL_PATTERN); + if (hiddenField != null) + return getSingleValue(hiddenField.getValue()); - return usernames.get(0); + return null; } /** - * Returns the password associated with the given record, as dictated by - * the {@link KeeperRecord#getPassword()}. + * Returns the password associated with the given record and matching the + * given label pattern. Both standard and custom fields are searched. As + * standard fields do not have labels, the label pattern is ignored for + * standard fields. Only "Password" and "Hidden" field types are + * considered. + * + * @param record + * The record to retrieve the password from. + * + * @param labelPattern + * The pattern to match against the labels of custom fields, or null if + * no label pattern match should be performed. + * + * @return + * The password associated with the given record, or null if the record + * has no associated password or multiple passwords. + */ + private String getPassword(KeeperRecord record, Pattern labelPattern) { + + Password passwordField = getField(record, Password.class, labelPattern); + if (passwordField != null) + return getSingleValue(passwordField.getValue()); + + HiddenField hiddenField = getField(record, HiddenField.class, labelPattern); + if (hiddenField != null) + return getSingleValue(hiddenField.getValue()); + + return null; + + } + + /** + * Returns the password associated with the given record. Both standard and + * custom fields are searched. Only "Password" and "Hidden" field types are + * considered. Custom fields must additionally have the label "password" + * (case-insensitive). * * @param record * The record to retrieve the password from. @@ -74,25 +309,16 @@ public class KsmRecordService { * has no associated password. */ public String getPassword(KeeperRecord record) { - - KeeperRecordData data = record.getData(); - - Password passwordField = (Password) data.getField(Password.class); - if (passwordField == null) - return null; - - List values = passwordField.getValue(); - if (values.size() != 1) - return null; - - return values.get(0); - + return getPassword(record, PASSWORD_LABEL_PATTERN); } /** * Returns the private key associated with the given record. If the record * has no associated private key, or multiple private keys, null is * returned. Private keys are retrieved from "KeyPairs" fields. + * Alternatively, private keys are retrieved from custom fields with the + * label "private key" (case-insensitive, space optional) if they are + * "KeyPairs", "Password", or "Hidden" fields. * * @param record * The record to retrieve the private key from. @@ -103,25 +329,23 @@ public class KsmRecordService { */ public String getPrivateKey(KeeperRecord record) { - KeeperRecordData data = record.getData(); + // Attempt to find single matching keypair field + KeyPairs keyPairsField = getField(record, KeyPairs.class, PRIVATE_KEY_LABEL_PATTERN); + if (keyPairsField != null) + return getSingleValue(keyPairsField.getValue(), KeyPair::getPrivateKey); - KeyPairs keyPairsField = (KeyPairs) data.getField(KeyPairs.class); - if (keyPairsField == null) - return null; - - List keyPairs = keyPairsField.getValue(); - if (keyPairs.size() != 1) - return null; - - return keyPairs.get(0).getPrivateKey(); + // Fall back to general password/hidden fields if not found or ambiguous + return getPassword(record, PRIVATE_KEY_LABEL_PATTERN); } /** * Returns the passphrase for the private key associated with the given - * record. Currently, this is simply dictated by {@link KeeperRecord#getPassword()}, - * as there is no specific association between private keys and passphrases - * in the "KeyPairs" field type. + * record. Both standard and custom fields are searched. Only "Password" + * and "Hidden" field types are considered. Custom fields must additionally + * have the label "passphrase" (case-insensitive). Note that there is no + * specific association between private keys and passphrases in the + * "KeyPairs" field type. * * @param record * The record to retrieve the passphrase from. @@ -131,7 +355,7 @@ public class KsmRecordService { * or null if there is no such passphrase associated with the record. */ public String getPassphrase(KeeperRecord record) { - return getPassword(record); + return getPassword(record, PASSPHRASE_LABEL_PATTERN); } } From 87b26fe2c832c2d6105c7387f56587e044859647 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 30/36] GUACAMOLE-641: Use record service to resolve hostname/username of records for later lookup. --- .../guacamole/vault/ksm/secret/KsmClient.java | 104 ++++++++---------- .../vault/ksm/secret/KsmRecordService.java | 47 ++++++++ 2 files changed, 91 insertions(+), 60 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java index 3969e18c6..64a2c6746 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java @@ -71,6 +71,12 @@ public class KsmClient { @Inject private KsmConfigurationService confService; + /** + * Service for retrieving data from records. + */ + @Inject + private KsmRecordService recordService; + /** * The publicly-accessible URL for Keeper's documentation covering Keeper * notation. @@ -226,28 +232,17 @@ public class KsmClient { cachedRecordsByUsername.clear(); // Store all records, sorting each into host-based and login-based - // buckets (note that a single record may be associated with - // multiple hosts and logins) + // buckets records.forEach(record -> { // Store based on UID ... cachedRecordsByUid.put(record.getRecordUid(), record); - // ... and standard fields ... - KeeperRecordData data = record.getData(); - addRecordForHosts(record, (Hosts) data.getField(Hosts.class)); - addRecordForLogin(record, (Login) data.getField(Login.class)); + // ... and hostname/address ... + addRecordForHost(record, recordService.getHostname(record)); - // ... and custom fields - List custom = data.getCustom(); - if (custom != null) { - custom.forEach(field -> { - if (field instanceof Hosts) - addRecordForHosts(record, (Hosts) field); - else if (field instanceof Login) - addRecordForLogin(record, (Login) field); - }); - } + // ... and username + addRecordForLogin(record, recordService.getUsername(record)); }); @@ -262,62 +257,51 @@ public class KsmClient { } /** - * Associates the given record with each of the hosts in the given Hosts - * field. The given Hosts field may be null. Both {@link #cachedRecordsByHost} - * and {@link #cachedAmbiguousHosts} are updated appropriately. The write - * lock of {@link #cacheLock} must already be acquired before invoking this - * function. - * - * @param record - * The record to associate with the hosts in the given field. - * - * @param hosts - * The Hosts field containing the hosts that the given record should be - * associated with. This may be null. - */ - private void addRecordForHosts(KeeperRecord record, Hosts hosts) { - - if (hosts == null) - return; - - hosts.getValue().stream().map(host -> host.getHostName()) - .forEachOrdered(hostname -> { - - KeeperRecord existing = cachedRecordsByHost.putIfAbsent(hostname, record); - if (existing != null && record != existing) - cachedAmbiguousHosts.add(hostname); - - }); - - } - - /** - * Associates the given record with each of the usernames in the given - * Login field. The given Hosts field may be null. Both - * {@link #cachedRecordsByUsername} and {@link #cachedAmbiguousUsernames} + * Associates the given record with the given hostname. The hostname may be + * null. Both {@link #cachedRecordsByHost} and {@link #cachedAmbiguousHosts} * are updated appropriately. The write lock of {@link #cacheLock} must * already be acquired before invoking this function. * * @param record * The record to associate with the hosts in the given field. * - * @param login - * The Login field containing the usernames that the given record - * should be associated with. This may be null. + * @param hostname + * The hostname/address that the given record should be associated + * with. This may be null. */ - private void addRecordForLogin(KeeperRecord record, Login login) { + private void addRecordForHost(KeeperRecord record, String hostname) { - if (login == null) + if (hostname == null) return; - login.getValue().stream() - .forEachOrdered(username -> { + KeeperRecord existing = cachedRecordsByHost.putIfAbsent(hostname, record); + if (existing != null && record != existing) + cachedAmbiguousHosts.add(hostname); - KeeperRecord existing = cachedRecordsByUsername.putIfAbsent(username, record); - if (existing != null && record != existing) - cachedAmbiguousUsernames.add(username); + } - }); + /** + * Associates the given record with the given username. The given username + * may be null. Both {@link #cachedRecordsByUsername} and + * {@link #cachedAmbiguousUsernames} are updated appropriately. The write + * lock of {@link #cacheLock} must already be acquired before invoking this + * function. + * + * @param record + * The record to associate with the given username. + * + * @param username + * The username that the given record should be associated with. This + * may be null. + */ + private void addRecordForLogin(KeeperRecord record, String username) { + + if (username == null) + return; + + KeeperRecord existing = cachedRecordsByUsername.putIfAbsent(username, record); + if (existing != null && record != existing) + cachedAmbiguousUsernames.add(username); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java index 5eef05ffd..8b14eea59 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java @@ -21,6 +21,8 @@ package org.apache.guacamole.vault.ksm.secret; import com.google.inject.Singleton; import com.keepersecurity.secretsManager.core.HiddenField; +import com.keepersecurity.secretsManager.core.Host; +import com.keepersecurity.secretsManager.core.Hosts; import com.keepersecurity.secretsManager.core.KeeperRecord; import com.keepersecurity.secretsManager.core.KeeperRecordData; import com.keepersecurity.secretsManager.core.KeeperRecordField; @@ -40,6 +42,13 @@ import java.util.regex.Pattern; @Singleton public class KsmRecordService { + /** + * Regular expression which matches the labels of custom fields containing + * hostnames/addresses. + */ + private static final Pattern HOSTNAME_LABEL_PATTERN = + Pattern.compile("hostname|(ip\\s*)?address", Pattern.CASE_INSENSITIVE); + /** * Regular expression which matches the labels of custom fields containing * usernames. @@ -225,6 +234,44 @@ public class KsmRecordService { } + /** + * Returns the single hostname (or address) associated with the given + * record. If the record has no associated hostname, or multiple hostnames, + * null is returned. Hostnames are retrieved from "Hosts" fields, as well + * as "Text" and "Hidden" fields that have the label "hostname", "address", + * or "ip address" (case-insensitive, space optional). + * + * @param record + * The record to retrieve the hostname from. + * + * @return + * The hostname associated with the given record, or null if the record + * has no associated hostname or multiple hostnames. + */ + public String getHostname(KeeperRecord record) { + + // Prefer standard login field + Hosts hostsField = getField(record, Hosts.class, null); + if (hostsField != null) + return getSingleValue(hostsField.getValue(), Host::getHostName); + + KeeperRecordData data = record.getData(); + List custom = data.getCustom(); + + // Use text "hostname" custom field as fallback ... + Text textField = getField(custom, Text.class, HOSTNAME_LABEL_PATTERN); + if (textField != null) + return getSingleValue(textField.getValue()); + + // ... or hidden "hostname" custom field + HiddenField hiddenField = getField(custom, HiddenField.class, HOSTNAME_LABEL_PATTERN); + if (hiddenField != null) + return getSingleValue(hiddenField.getValue()); + + return null; + + } + /** * Returns the single username associated with the given record. If the * record has no associated username, or multiple usernames, null is From 1cfd2ee8356a55dae903d313bab81f6683701485 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 31/36] GUACAMOLE-641: Index records by username ONLY if not related to a hostname. Doing otherwise would mean that a particular user would never be able to be associated with a specific password/key by their username if they have any explicit server-specific account. --- .../apache/guacamole/vault/ksm/secret/KsmClient.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java index 64a2c6746..4812b904c 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java @@ -238,11 +238,15 @@ public class KsmClient { // Store based on UID ... cachedRecordsByUid.put(record.getRecordUid(), record); - // ... and hostname/address ... - addRecordForHost(record, recordService.getHostname(record)); + // ... and hostname/address + String hostname = recordService.getHostname(record); + addRecordForHost(record, hostname); - // ... and username - addRecordForLogin(record, recordService.getUsername(record)); + // Store based on username ONLY if no hostname (will otherwise + // result in ambiguous entries for servers tied to identical + // accounts) + if (hostname == null) + addRecordForLogin(record, recordService.getUsername(record)); }); From 46501f4b63f90c9b7b81e9473599e6c396e7382d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 32/36] GUACAMOLE-641: Correct standard vs. custom field logic for complex retrievals. When retrieving a contextual field like "passphrase", which does not have a typed representation different from "password" or "hidden", the contexts where the field's identity is truly known should be preferred ("password" field of a record with a "keypair" field, which MUST be the key passphrase). When venturing outside well-known contexts, custom fields should be preferred when their standard counterparts would already have well-established meanings that differ from the requested secret (again: "password" of a record with "keypair"). If this is not done, things like retrieving the private key from a "Login" record fail, as one of the possible storage mechanisms for a private key is a hidden or password field, which pulls the user's password instead of their key. In this case, the correct behavior is to pull the typed value ("keypair") if available, and use custom fields ONLY otherwise, as those fields have labels that can establish context. In no other case would it be reliable to assume that a hidden/password field actually contains a private key. --- .../vault/ksm/secret/KsmRecordService.java | 92 +++++++++++-------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java index 8b14eea59..a82434300 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java @@ -310,38 +310,6 @@ public class KsmRecordService { } - /** - * Returns the password associated with the given record and matching the - * given label pattern. Both standard and custom fields are searched. As - * standard fields do not have labels, the label pattern is ignored for - * standard fields. Only "Password" and "Hidden" field types are - * considered. - * - * @param record - * The record to retrieve the password from. - * - * @param labelPattern - * The pattern to match against the labels of custom fields, or null if - * no label pattern match should be performed. - * - * @return - * The password associated with the given record, or null if the record - * has no associated password or multiple passwords. - */ - private String getPassword(KeeperRecord record, Pattern labelPattern) { - - Password passwordField = getField(record, Password.class, labelPattern); - if (passwordField != null) - return getSingleValue(passwordField.getValue()); - - HiddenField hiddenField = getField(record, HiddenField.class, labelPattern); - if (hiddenField != null) - return getSingleValue(hiddenField.getValue()); - - return null; - - } - /** * Returns the password associated with the given record. Both standard and * custom fields are searched. Only "Password" and "Hidden" field types are @@ -356,7 +324,17 @@ public class KsmRecordService { * has no associated password. */ public String getPassword(KeeperRecord record) { - return getPassword(record, PASSWORD_LABEL_PATTERN); + + Password passwordField = getField(record, Password.class, PASSWORD_LABEL_PATTERN); + if (passwordField != null) + return getSingleValue(passwordField.getValue()); + + HiddenField hiddenField = getField(record, HiddenField.class, PASSWORD_LABEL_PATTERN); + if (hiddenField != null) + return getSingleValue(hiddenField.getValue()); + + return null; + } /** @@ -381,8 +359,20 @@ public class KsmRecordService { if (keyPairsField != null) return getSingleValue(keyPairsField.getValue(), KeyPair::getPrivateKey); - // Fall back to general password/hidden fields if not found or ambiguous - return getPassword(record, PRIVATE_KEY_LABEL_PATTERN); + KeeperRecordData data = record.getData(); + List custom = data.getCustom(); + + // Use password "private key" custom field as fallback ... + Password passwordField = getField(custom, Password.class, PRIVATE_KEY_LABEL_PATTERN); + if (passwordField != null) + return getSingleValue(passwordField.getValue()); + + // ... or hidden "private key" custom field + HiddenField hiddenField = getField(custom, HiddenField.class, PRIVATE_KEY_LABEL_PATTERN); + if (hiddenField != null) + return getSingleValue(hiddenField.getValue()); + + return null; } @@ -402,7 +392,37 @@ public class KsmRecordService { * or null if there is no such passphrase associated with the record. */ public String getPassphrase(KeeperRecord record) { - return getPassword(record, PASSPHRASE_LABEL_PATTERN); + + KeeperRecordData data = record.getData(); + List fields = data.getFields(); + List custom = data.getCustom(); + + // For records with a standard keypair field, the passphrase is the + // standard password field + if (getField(fields, KeyPairs.class, null) != null) { + Password passwordField = getField(fields, Password.class, null); + if (passwordField != null) + return getSingleValue(passwordField.getValue()); + } + + // For records WITHOUT a standard keypair field, the passphrase can + // only reasonably be a custom field (consider a "Login" record with + // a pair of custom hidden fields for the private key and passphrase: + // the standard password field of the "Login" record refers to the + // user's own password, if any, not the passphrase of their key) + + // Use password "private key" custom field as fallback ... + Password passwordField = getField(custom, Password.class, PASSPHRASE_LABEL_PATTERN); + if (passwordField != null) + return getSingleValue(passwordField.getValue()); + + // ... or hidden "private key" custom field + HiddenField hiddenField = getField(custom, HiddenField.class, PASSPHRASE_LABEL_PATTERN); + if (hiddenField != null) + return getSingleValue(hiddenField.getValue()); + + return null; + } } From 86d1de5f2caadd28430f60a239e9299b8afd2053 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 33/36] GUACAMOLE-641: Automatically pull Guacamole properties from vault. --- .../AzureKeyVaultConfigurationService.java | 18 ++++- .../vault/VaultAuthenticationProvider.java | 16 +++- .../vault/conf/VaultConfigurationService.java | 77 ++++++++++++++++++- .../ksm/conf/KsmConfigurationService.java | 18 ++++- 4 files changed, 118 insertions(+), 11 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java index bed7b264f..16620c2f6 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java @@ -47,6 +47,13 @@ public class AzureKeyVaultConfigurationService extends VaultConfigurationService */ private static final String TOKEN_MAPPING_FILENAME = "azure-keyvault-token-mapping.yml"; + /** + * The name of the properties file containing Guacamole configuration + * properties whose values are the names of corresponding secrets within + * Azure Key Vault. + */ + private static final String PROPERTIES_FILENAME = "guacamole.properties.azure"; + /** * The number of milliseconds that each retrieved secret should be cached * for. @@ -101,12 +108,15 @@ public class AzureKeyVaultConfigurationService extends VaultConfigurationService /** * Creates a new AzureKeyVaultConfigurationService which reads the token - * mapping from "azure-keyvault-token-mapping.yml". The token mapping is a - * YAML file which lists each connection parameter token and the name of - * the secret from which the value for that token should be read. + * mapping from "azure-keyvault-token-mapping.yml" and properties from + * "guacamole.properties.azure". The token mapping is a YAML file which + * lists each connection parameter token and the name of the secret from + * which the value for that token should be read, while the properties + * file is an alternative to guacamole.properties where each property + * value is the name of a secret containing the actual value. */ public AzureKeyVaultConfigurationService() { - super(TOKEN_MAPPING_FILENAME); + super(TOKEN_MAPPING_FILENAME, PROPERTIES_FILENAME); } /** diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProvider.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProvider.java index a18b9c05a..440ef95d1 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProvider.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProvider.java @@ -22,10 +22,12 @@ package org.apache.guacamole.vault; import com.google.inject.Guice; import com.google.inject.Injector; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.auth.AbstractAuthenticationProvider; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.vault.conf.VaultConfigurationService; import org.apache.guacamole.vault.user.VaultUserContextFactory; /** @@ -47,10 +49,22 @@ public abstract class VaultAuthenticationProvider * * @param module * The module to use to configure dependency injection. + * + * @throws GuacamoleException + * If the properties file containing vault-mapped Guacamole + * configuration properties exists but cannot be read. */ - protected VaultAuthenticationProvider(VaultAuthenticationProviderModule module) { + protected VaultAuthenticationProvider(VaultAuthenticationProviderModule module) + throws GuacamoleException { + Injector injector = Guice.createInjector(module); this.userContextFactory = injector.getInstance(VaultUserContextFactory.class); + + // Automatically pull properties from vault + Environment environment = injector.getInstance(Environment.class); + VaultConfigurationService confService = injector.getInstance(VaultConfigurationService.class); + environment.addGuacamoleProperties(confService.getProperties()); + } @Override diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java index 36a74ea4c..a666a7b97 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/conf/VaultConfigurationService.java @@ -27,10 +27,16 @@ import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ExecutionException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.FileGuacamoleProperties; +import org.apache.guacamole.properties.GuacamoleProperties; +import org.apache.guacamole.properties.PropertiesGuacamoleProperties; import org.apache.guacamole.vault.VaultAuthenticationProviderModule; +import org.apache.guacamole.vault.secret.VaultSecretService; /** * Base class for services which retrieve key vault configuration information. @@ -47,6 +53,9 @@ public abstract class VaultConfigurationService { @Inject private Environment environment; + @Inject + private VaultSecretService secretService; + /** * ObjectMapper for deserializing YAML. */ @@ -58,15 +67,30 @@ public abstract class VaultConfigurationService { */ private final String tokenMappingFilename; + /** + * The name of the properties file containing Guacamole configuration + * properties. Unlike guacamole.properties, the values of these properties + * are read from the vault. Each property is expected to contain a secret + * name instead of a property value. + */ + private final String propertiesFilename; + /** * Creates a new VaultConfigurationService which retrieves the token/secret - * mapping from a YAML file having the given name. + * mappings and Guacamole configuration properties from the files with the + * given names. * * @param tokenMappingFilename * The name of the YAML file containing the token/secret mapping. + * + * @param propertiesFilename + * The name of the properties file containing Guacamole configuration + * properties whose values are the names of corresponding secrets. */ - protected VaultConfigurationService(String tokenMappingFilename) { + protected VaultConfigurationService(String tokenMappingFilename, + String propertiesFilename) { this.tokenMappingFilename = tokenMappingFilename; + this.propertiesFilename = propertiesFilename; } /** @@ -114,4 +138,53 @@ public abstract class VaultConfigurationService { } + /** + * Returns a GuacamoleProperties instance which automatically reads the + * values of requested properties from the vault. The name of the secret + * corresponding to a property stored in the vault is defined via the + * properties filename supplied at construction time. + * + * @return + * A GuacamoleProperties instance which automatically reads property + * values from the vault. + * + * @throws GuacamoleException + * If the properties file containing the property/secret mappings + * exists but cannot be read. + */ + public GuacamoleProperties getProperties() throws GuacamoleException { + + // Use empty properties if file cannot be found + File propFile = new File(environment.getGuacamoleHome(), propertiesFilename); + if (!propFile.exists()) + return new PropertiesGuacamoleProperties(new Properties()); + + // Automatically pull properties from vault + return new FileGuacamoleProperties(propFile) { + + @Override + public String getProperty(String name) throws GuacamoleException { + try { + + String secretName = super.getProperty(name); + if (secretName == null) + return null; + + return secretService.getValue(secretName).get(); + + } + catch (InterruptedException | ExecutionException e) { + + if (e.getCause() instanceof GuacamoleException) + throw (GuacamoleException) e; + + throw new GuacamoleServerException(String.format("Property " + + "\"%s\" could not be retrieved from the vault.", name), e); + } + } + + }; + + } + } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigurationService.java index 398b9bb3a..38bcaaef1 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigurationService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmConfigurationService.java @@ -46,6 +46,13 @@ public class KsmConfigurationService extends VaultConfigurationService { */ private static final String TOKEN_MAPPING_FILENAME = "ksm-token-mapping.yml"; + /** + * The name of the properties file containing Guacamole configuration + * properties whose values are the names of corresponding secrets within + * Keeper Secrets Manager. + */ + private static final String PROPERTIES_FILENAME = "guacamole.properties.ksm"; + /** * The base64-encoded configuration information generated by the Keeper * Commander CLI tool. @@ -71,12 +78,15 @@ public class KsmConfigurationService extends VaultConfigurationService { /** * Creates a new KsmConfigurationService which reads the configuration - * from "ksm-token-mapping.yml". The token mapping is a YAML file which - * lists each connection parameter token and the title of the secret from - * which the value for that token should be read. + * from "ksm-token-mapping.yml" and properties from + * "guacamole.properties.ksm". The token mapping is a YAML file which lists + * each connection parameter token and the name of the secret from which + * the value for that token should be read, while the properties file is an + * alternative to guacamole.properties where each property value is the + * name of a secret containing the actual value. */ public KsmConfigurationService() { - super(TOKEN_MAPPING_FILENAME); + super(TOKEN_MAPPING_FILENAME, PROPERTIES_FILENAME); } /** From e89a65586c653afbebc31b6a89237a46ea93f352 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 34/36] GUACAMOLE-641: Alternatively download .pem files for private keys. --- .../guacamole/vault/ksm/secret/KsmClient.java | 15 +-- .../vault/ksm/secret/KsmRecordService.java | 103 ++++++++++++++++-- .../vault/ksm/secret/KsmSecretService.java | 5 +- 3 files changed, 98 insertions(+), 25 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java index 4812b904c..2372dcb29 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java @@ -24,13 +24,10 @@ import com.google.inject.Singleton; import com.keepersecurity.secretsManager.core.Hosts; import com.keepersecurity.secretsManager.core.KeeperFile; import com.keepersecurity.secretsManager.core.KeeperRecord; -import com.keepersecurity.secretsManager.core.KeeperRecordData; -import com.keepersecurity.secretsManager.core.KeeperRecordField; import com.keepersecurity.secretsManager.core.KeeperSecrets; import com.keepersecurity.secretsManager.core.Login; import com.keepersecurity.secretsManager.core.Notation; import com.keepersecurity.secretsManager.core.SecretsManager; -import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -446,16 +443,10 @@ public class KsmClient { cacheLock.readLock().lock(); try { + // Retrieve any relevant file asynchronously Matcher fileNotationMatcher = KEEPER_FILE_NOTATION.matcher(notation); - if (fileNotationMatcher.matches()) { - - // Retrieve any relevant file asynchronously - KeeperFile file = Notation.getFile(cachedSecrets, notation); - return CompletableFuture.supplyAsync(() -> { - return new String(SecretsManager.downloadFile(file), StandardCharsets.UTF_8); - }); - - } + if (fileNotationMatcher.matches()) + return recordService.download(Notation.getFile(cachedSecrets, notation)); // Retrieve string values synchronously return CompletableFuture.completedFuture(Notation.getValue(cachedSecrets, notation)); diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java index a82434300..e2543ba1b 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java @@ -23,6 +23,7 @@ import com.google.inject.Singleton; import com.keepersecurity.secretsManager.core.HiddenField; import com.keepersecurity.secretsManager.core.Host; import com.keepersecurity.secretsManager.core.Hosts; +import com.keepersecurity.secretsManager.core.KeeperFile; import com.keepersecurity.secretsManager.core.KeeperRecord; import com.keepersecurity.secretsManager.core.KeeperRecordData; import com.keepersecurity.secretsManager.core.KeeperRecordField; @@ -30,8 +31,12 @@ import com.keepersecurity.secretsManager.core.KeyPair; import com.keepersecurity.secretsManager.core.KeyPairs; import com.keepersecurity.secretsManager.core.Login; import com.keepersecurity.secretsManager.core.Password; +import com.keepersecurity.secretsManager.core.SecretsManager; import com.keepersecurity.secretsManager.core.Text; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -77,6 +82,13 @@ public class KsmRecordService { private static final Pattern PRIVATE_KEY_LABEL_PATTERN = Pattern.compile("private\\s*key", Pattern.CASE_INSENSITIVE); + /** + * Regular expression which matches the filenames of private keys attached + * to Keeper records. + */ + private static final Pattern PRIVATE_KEY_FILENAME_PATTERN = + Pattern.compile(".*\\.pem", Pattern.CASE_INSENSITIVE); + /** * Returns the single value stored in the given list. If the list is empty * or contains multiple values, null is returned. @@ -234,6 +246,70 @@ public class KsmRecordService { } + /** + * Returns the file attached to the give Keeper record whose filename + * matches the given pattern. If there are no such files, or multiple such + * files, null is returned. + * + * @param record + * The record to retrieve the file from. + * + * @param filenamePattern + * The pattern to match filenames against. + * + * @return + * The single matching file attached to the given Keeper record, or + * null if there is not exactly one matching file. + */ + private KeeperFile getFile(KeeperRecord record, Pattern filenamePattern) { + + List files = record.getFiles(); + if (files == null) + return null; + + KeeperFile foundFile = null; + for (KeeperFile file : files) { + + // Ignore files whose filenames do not match + Matcher filenameMatcher = filenamePattern.matcher(file.getData().getName()); + if (!filenameMatcher.matches()) + continue; + + // Ignore ambiguous fields + if (foundFile != null) + return null; + + foundFile = file; + + } + + return foundFile; + + } + + /** + * Downloads the given file from the Keeper vault asynchronously. All files + * are read as UTF-8. + * + * @param file + * The file to download, which may be null. + * + * @return + * A Future which resolves with the contents of the file once + * downloaded. If no file was provided (file was null), this Future + * resolves with null. + */ + public Future download(final KeeperFile file) { + + if (file == null) + return CompletableFuture.completedFuture(null); + + return CompletableFuture.supplyAsync(() -> { + return new String(SecretsManager.downloadFile(file), StandardCharsets.UTF_8); + }); + + } + /** * Returns the single hostname (or address) associated with the given * record. If the record has no associated hostname, or multiple hostnames, @@ -341,23 +417,30 @@ public class KsmRecordService { * Returns the private key associated with the given record. If the record * has no associated private key, or multiple private keys, null is * returned. Private keys are retrieved from "KeyPairs" fields. - * Alternatively, private keys are retrieved from custom fields with the - * label "private key" (case-insensitive, space optional) if they are - * "KeyPairs", "Password", or "Hidden" fields. + * Alternatively, private keys are retrieved from PEM-type attachments or + * custom fields with the label "private key" (case-insensitive, space + * optional) if they are "KeyPairs", "Password", or "Hidden" fields. If + * file downloads are required, they will be performed asynchronously. * * @param record * The record to retrieve the private key from. * * @return - * The private key associated with the given record, or null if the - * record has no associated private key or multiple private keys. + * A Future which resolves with the private key associated with the + * given record. If the record has no associated private key or + * multiple private keys, the returned Future will resolve to null. */ - public String getPrivateKey(KeeperRecord record) { + public Future getPrivateKey(KeeperRecord record) { // Attempt to find single matching keypair field KeyPairs keyPairsField = getField(record, KeyPairs.class, PRIVATE_KEY_LABEL_PATTERN); if (keyPairsField != null) - return getSingleValue(keyPairsField.getValue(), KeyPair::getPrivateKey); + return CompletableFuture.completedFuture(getSingleValue(keyPairsField.getValue(), KeyPair::getPrivateKey)); + + // Lacking a typed keypair field, prefer a PEM-type attachment + KeeperFile keyFile = getFile(record, PRIVATE_KEY_FILENAME_PATTERN); + if (keyFile != null) + return download(keyFile); KeeperRecordData data = record.getData(); List custom = data.getCustom(); @@ -365,14 +448,14 @@ public class KsmRecordService { // Use password "private key" custom field as fallback ... Password passwordField = getField(custom, Password.class, PRIVATE_KEY_LABEL_PATTERN); if (passwordField != null) - return getSingleValue(passwordField.getValue()); + return CompletableFuture.completedFuture(getSingleValue(passwordField.getValue())); // ... or hidden "private key" custom field HiddenField hiddenField = getField(custom, HiddenField.class, PRIVATE_KEY_LABEL_PATTERN); if (hiddenField != null) - return getSingleValue(hiddenField.getValue()); + return CompletableFuture.completedFuture(getSingleValue(hiddenField.getValue())); - return null; + return CompletableFuture.completedFuture(null); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java index d7b4deb50..824f9e54e 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java @@ -109,9 +109,8 @@ public class KsmSecretService implements VaultSecretService { tokens.put(prefix + "PASSPHRASE", CompletableFuture.completedFuture(passphrase)); // Private key of server-related record - String privateKey = recordService.getPrivateKey(record); - if (privateKey != null) - tokens.put(prefix + "KEY", CompletableFuture.completedFuture(privateKey)); + Future privateKey = recordService.getPrivateKey(record); + tokens.put(prefix + "KEY", privateKey); } From 979505bb58f789bb63b4211ff3fcdef5b17f8f74 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Jan 2022 15:23:41 -0800 Subject: [PATCH 35/36] GUACAMOLE-641: Remove Azure Key Vault extension until license text can be obtained. --- doc/licenses/adal4j-1.6.7/LICENSE | 21 -- doc/licenses/adal4j-1.6.7/README | 8 - doc/licenses/adal4j-1.6.7/dep-coordinates.txt | 1 - doc/licenses/apache-commons-lang-3.8.1/NOTICE | 5 - doc/licenses/apache-commons-lang-3.8.1/README | 8 - .../dep-coordinates.txt | 1 - doc/licenses/asm-8.0.1/LICENSE.txt | 28 --- doc/licenses/asm-8.0.1/README | 8 - doc/licenses/asm-8.0.1/dep-coordinates.txt | 1 - .../autorest-client-runtime-1.7.4/LICENSE | 21 -- .../autorest-client-runtime-1.7.4/README | 9 - .../dep-coordinates.txt | 2 - .../azure-annotations-1.10.0/License.txt | 28 --- doc/licenses/azure-annotations-1.10.0/README | 9 - .../dep-coordinates.txt | 1 - .../azure-sdk-for-java-1.2.4/LICENSE.txt | 21 -- doc/licenses/azure-sdk-for-java-1.2.4/README | 8 - .../dep-coordinates.txt | 5 - doc/licenses/gson-2.8.0/README | 8 - doc/licenses/gson-2.8.0/dep-coordinates.txt | 1 - .../jackson-2.13.1/dep-coordinates.txt | 1 - doc/licenses/joda-time-2.10.8/NOTICE | 2 - doc/licenses/joda-time-2.10.8/README | 8 - .../joda-time-2.10.8/dep-coordinates.txt | 1 - doc/licenses/json-smart-2.4.2/LICENSE | 202 ------------------ doc/licenses/json-smart-2.4.2/README | 8 - .../json-smart-2.4.2/dep-coordinates.txt | 2 - doc/licenses/lang-tag-1.5/README | 8 - doc/licenses/lang-tag-1.5/dep-coordinates.txt | 1 - doc/licenses/nimbus-content-type-2.1/README | 8 - .../dep-coordinates.txt | 1 - doc/licenses/nimbus-jose-jwt-9.8.1/README | 8 - .../nimbus-jose-jwt-9.8.1/dep-coordinates.txt | 1 - doc/licenses/oauth2-oidc-sdk-9.4/README | 9 - .../oauth2-oidc-sdk-9.4/dep-coordinates.txt | 1 - doc/licenses/okhttp-3.14.7/README | 8 - .../okhttp-3.14.7/dep-coordinates.txt | 3 - doc/licenses/okio-1.17.2/README | 8 - doc/licenses/okio-1.17.2/dep-coordinates.txt | 1 - doc/licenses/retrofit-2.7.2/README | 8 - .../retrofit-2.7.2/dep-coordinates.txt | 3 - doc/licenses/rxjava-1.3.8/LICENSE | 202 ------------------ doc/licenses/rxjava-1.3.8/README | 8 - doc/licenses/rxjava-1.3.8/dep-coordinates.txt | 1 - .../stephenc-jcip-annotations-1.0-1/README | 8 - .../dep-coordinates.txt | 1 - .../modules/guacamole-vault-azure/.ratignore | 0 .../modules/guacamole-vault-azure/pom.xml | 194 ----------------- .../AzureKeyVaultAuthenticationProvider.java | 47 ---- ...eKeyVaultAuthenticationProviderModule.java | 61 ------ .../AzureKeyVaultAuthenticationException.java | 57 ----- .../AzureKeyVaultConfigurationService.java | 174 --------------- .../azure/conf/AzureKeyVaultCredentials.java | 115 ---------- .../secret/AzureKeyVaultSecretService.java | 132 ------------ .../src/main/resources/guac-manifest.json | 16 -- .../modules/guacamole-vault-dist/pom.xml | 7 - .../src/main/assembly/dist.xml | 8 - extensions/guacamole-vault/pom.xml | 1 - pom.xml | 5 - 59 files changed, 1523 deletions(-) delete mode 100644 doc/licenses/adal4j-1.6.7/LICENSE delete mode 100644 doc/licenses/adal4j-1.6.7/README delete mode 100644 doc/licenses/adal4j-1.6.7/dep-coordinates.txt delete mode 100644 doc/licenses/apache-commons-lang-3.8.1/NOTICE delete mode 100644 doc/licenses/apache-commons-lang-3.8.1/README delete mode 100644 doc/licenses/apache-commons-lang-3.8.1/dep-coordinates.txt delete mode 100644 doc/licenses/asm-8.0.1/LICENSE.txt delete mode 100644 doc/licenses/asm-8.0.1/README delete mode 100644 doc/licenses/asm-8.0.1/dep-coordinates.txt delete mode 100644 doc/licenses/autorest-client-runtime-1.7.4/LICENSE delete mode 100644 doc/licenses/autorest-client-runtime-1.7.4/README delete mode 100644 doc/licenses/autorest-client-runtime-1.7.4/dep-coordinates.txt delete mode 100644 doc/licenses/azure-annotations-1.10.0/License.txt delete mode 100644 doc/licenses/azure-annotations-1.10.0/README delete mode 100644 doc/licenses/azure-annotations-1.10.0/dep-coordinates.txt delete mode 100644 doc/licenses/azure-sdk-for-java-1.2.4/LICENSE.txt delete mode 100644 doc/licenses/azure-sdk-for-java-1.2.4/README delete mode 100644 doc/licenses/azure-sdk-for-java-1.2.4/dep-coordinates.txt delete mode 100644 doc/licenses/gson-2.8.0/README delete mode 100644 doc/licenses/gson-2.8.0/dep-coordinates.txt delete mode 100644 doc/licenses/joda-time-2.10.8/NOTICE delete mode 100644 doc/licenses/joda-time-2.10.8/README delete mode 100644 doc/licenses/joda-time-2.10.8/dep-coordinates.txt delete mode 100644 doc/licenses/json-smart-2.4.2/LICENSE delete mode 100644 doc/licenses/json-smart-2.4.2/README delete mode 100644 doc/licenses/json-smart-2.4.2/dep-coordinates.txt delete mode 100644 doc/licenses/lang-tag-1.5/README delete mode 100644 doc/licenses/lang-tag-1.5/dep-coordinates.txt delete mode 100644 doc/licenses/nimbus-content-type-2.1/README delete mode 100644 doc/licenses/nimbus-content-type-2.1/dep-coordinates.txt delete mode 100644 doc/licenses/nimbus-jose-jwt-9.8.1/README delete mode 100644 doc/licenses/nimbus-jose-jwt-9.8.1/dep-coordinates.txt delete mode 100644 doc/licenses/oauth2-oidc-sdk-9.4/README delete mode 100644 doc/licenses/oauth2-oidc-sdk-9.4/dep-coordinates.txt delete mode 100644 doc/licenses/okhttp-3.14.7/README delete mode 100644 doc/licenses/okhttp-3.14.7/dep-coordinates.txt delete mode 100644 doc/licenses/okio-1.17.2/README delete mode 100644 doc/licenses/okio-1.17.2/dep-coordinates.txt delete mode 100644 doc/licenses/retrofit-2.7.2/README delete mode 100644 doc/licenses/retrofit-2.7.2/dep-coordinates.txt delete mode 100644 doc/licenses/rxjava-1.3.8/LICENSE delete mode 100644 doc/licenses/rxjava-1.3.8/README delete mode 100644 doc/licenses/rxjava-1.3.8/dep-coordinates.txt delete mode 100644 doc/licenses/stephenc-jcip-annotations-1.0-1/README delete mode 100644 doc/licenses/stephenc-jcip-annotations-1.0-1/dep-coordinates.txt delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/.ratignore delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/pom.xml delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProvider.java delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProviderModule.java delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultAuthenticationException.java delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultCredentials.java delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java delete mode 100644 extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/resources/guac-manifest.json diff --git a/doc/licenses/adal4j-1.6.7/LICENSE b/doc/licenses/adal4j-1.6.7/LICENSE deleted file mode 100644 index 48bc6bb49..000000000 --- a/doc/licenses/adal4j-1.6.7/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/doc/licenses/adal4j-1.6.7/README b/doc/licenses/adal4j-1.6.7/README deleted file mode 100644 index 34062836d..000000000 --- a/doc/licenses/adal4j-1.6.7/README +++ /dev/null @@ -1,8 +0,0 @@ -adal4j (https://github.com/AzureAD/azure-activedirectory-library-for-java) --------------------------------------------------------------------------- - - Version: 1.6.7 - From: 'Microsoft Corporation' (https://microsoft.com/) - License(s): - MIT (bundled/adal4j-1.6.7/LICENSE) - diff --git a/doc/licenses/adal4j-1.6.7/dep-coordinates.txt b/doc/licenses/adal4j-1.6.7/dep-coordinates.txt deleted file mode 100644 index e8e0f3b2f..000000000 --- a/doc/licenses/adal4j-1.6.7/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.microsoft.azure:adal4j:jar:1.6.7 diff --git a/doc/licenses/apache-commons-lang-3.8.1/NOTICE b/doc/licenses/apache-commons-lang-3.8.1/NOTICE deleted file mode 100644 index 0f4ac594a..000000000 --- a/doc/licenses/apache-commons-lang-3.8.1/NOTICE +++ /dev/null @@ -1,5 +0,0 @@ -Apache Commons Lang -Copyright 2001-2018 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). diff --git a/doc/licenses/apache-commons-lang-3.8.1/README b/doc/licenses/apache-commons-lang-3.8.1/README deleted file mode 100644 index d8cf381ef..000000000 --- a/doc/licenses/apache-commons-lang-3.8.1/README +++ /dev/null @@ -1,8 +0,0 @@ -Apache Commons Lang (http://commons.apache.org/proper/commons-lang/) --------------------------------------------------------------------- - - Version: 3.8.1 - From: 'Apache Software Foundation' (https://www.apache.org/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/apache-commons-lang-3.8.1/dep-coordinates.txt b/doc/licenses/apache-commons-lang-3.8.1/dep-coordinates.txt deleted file mode 100644 index f3305d051..000000000 --- a/doc/licenses/apache-commons-lang-3.8.1/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -org.apache.commons:commons-lang3:jar:3.8.1 diff --git a/doc/licenses/asm-8.0.1/LICENSE.txt b/doc/licenses/asm-8.0.1/LICENSE.txt deleted file mode 100644 index 4d191851a..000000000 --- a/doc/licenses/asm-8.0.1/LICENSE.txt +++ /dev/null @@ -1,28 +0,0 @@ - - ASM: a very small and fast Java bytecode manipulation framework - Copyright (c) 2000-2011 INRIA, France Telecom - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holders nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/licenses/asm-8.0.1/README b/doc/licenses/asm-8.0.1/README deleted file mode 100644 index 304b95281..000000000 --- a/doc/licenses/asm-8.0.1/README +++ /dev/null @@ -1,8 +0,0 @@ -ASM (https://asm.ow2.io/) -------------------------- - - Version: 8.0.1 - From: 'INRIA, France Telecom' - License(s): - BSD 3-clause (bundled/asm-8.0.1/LICENSE.txt) - diff --git a/doc/licenses/asm-8.0.1/dep-coordinates.txt b/doc/licenses/asm-8.0.1/dep-coordinates.txt deleted file mode 100644 index cf52dc1a7..000000000 --- a/doc/licenses/asm-8.0.1/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -org.ow2.asm:asm:jar:8.0.1 diff --git a/doc/licenses/autorest-client-runtime-1.7.4/LICENSE b/doc/licenses/autorest-client-runtime-1.7.4/LICENSE deleted file mode 100644 index 4918d653b..000000000 --- a/doc/licenses/autorest-client-runtime-1.7.4/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Microsoft Azure - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/doc/licenses/autorest-client-runtime-1.7.4/README b/doc/licenses/autorest-client-runtime-1.7.4/README deleted file mode 100644 index e8a65f14f..000000000 --- a/doc/licenses/autorest-client-runtime-1.7.4/README +++ /dev/null @@ -1,9 +0,0 @@ -AutoRest Client Runtimes for Java -(https://github.com/Azure/autorest-clientruntime-for-java) ----------------------------------------------------------- - - Version: 1.7.4 - From: 'Microsoft Azure' (https://azure.microsoft.com/) - License(s): - MIT (bundled/autorest-client-runtime-1.7.4/LICENSE) - diff --git a/doc/licenses/autorest-client-runtime-1.7.4/dep-coordinates.txt b/doc/licenses/autorest-client-runtime-1.7.4/dep-coordinates.txt deleted file mode 100644 index 5d1dc913f..000000000 --- a/doc/licenses/autorest-client-runtime-1.7.4/dep-coordinates.txt +++ /dev/null @@ -1,2 +0,0 @@ -com.microsoft.rest:client-runtime:jar:1.7.4 -com.microsoft.azure:azure-client-runtime:jar:1.7.4 diff --git a/doc/licenses/azure-annotations-1.10.0/License.txt b/doc/licenses/azure-annotations-1.10.0/License.txt deleted file mode 100644 index fbe8e19b3..000000000 --- a/doc/licenses/azure-annotations-1.10.0/License.txt +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -NOTE: The above has been extracted from the source of the "azure-annotations" -library, as may be downloaded from Maven Central: - -https://search.maven.org/remotecontent?filepath=com/microsoft/azure/azure-annotations/1.10.0/azure-annotations-1.10.0-sources.jar - -Unfortunately, the "License.txt" file noted is not included with the source -.jar, and the GitHub repository referenced by the pom.xml of -"azure-annotations" is not publicly visible: - -https://github.com/Microsoft/java-api-annotations - -I (Mike Jumper) have reached out to Microsoft to correct this and to request a -copy of the "License.txt" file if access to this repository cannot be fixed in -the near future. Until then, the above should serve as reasonable confirmation -that this library is indeed (1) licensed under the MIT license and (2) -copyright Microsoft Corporation. - -For reference, the terms of the open source license widely known as the "MIT -license" can be found here: - -https://opensource.org/licenses/MIT - diff --git a/doc/licenses/azure-annotations-1.10.0/README b/doc/licenses/azure-annotations-1.10.0/README deleted file mode 100644 index 183f0f76e..000000000 --- a/doc/licenses/azure-annotations-1.10.0/README +++ /dev/null @@ -1,9 +0,0 @@ -Microsoft Azure SDK Annotations -(https://github.com/Microsoft/java-api-annotations) ---------------------------------------------------- - - Version: 1.10.0 - From: 'Microsoft Corporation' (https://microsoft.com/) - License(s): - MIT (bundled/azure-annotations-1.10.0/License.txt) - diff --git a/doc/licenses/azure-annotations-1.10.0/dep-coordinates.txt b/doc/licenses/azure-annotations-1.10.0/dep-coordinates.txt deleted file mode 100644 index f96581d3c..000000000 --- a/doc/licenses/azure-annotations-1.10.0/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.microsoft.azure:azure-annotations:jar:1.10.0 diff --git a/doc/licenses/azure-sdk-for-java-1.2.4/LICENSE.txt b/doc/licenses/azure-sdk-for-java-1.2.4/LICENSE.txt deleted file mode 100644 index 49d21669a..000000000 --- a/doc/licenses/azure-sdk-for-java-1.2.4/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/doc/licenses/azure-sdk-for-java-1.2.4/README b/doc/licenses/azure-sdk-for-java-1.2.4/README deleted file mode 100644 index 9e6e3fd08..000000000 --- a/doc/licenses/azure-sdk-for-java-1.2.4/README +++ /dev/null @@ -1,8 +0,0 @@ -Azure SDK for Java (https://github.com/Azure/azure-sdk-for-java/) ------------------------------------------------------------------ - - Version: 1.2.4 - From: 'Microsoft' (https://microsoft.com/) - License(s): - MIT (bundled/azure-sdk-for-java-1.2.4/LICENSE.txt) - diff --git a/doc/licenses/azure-sdk-for-java-1.2.4/dep-coordinates.txt b/doc/licenses/azure-sdk-for-java-1.2.4/dep-coordinates.txt deleted file mode 100644 index 9bfa04c27..000000000 --- a/doc/licenses/azure-sdk-for-java-1.2.4/dep-coordinates.txt +++ /dev/null @@ -1,5 +0,0 @@ -com.microsoft.azure:azure-keyvault-core:jar:1.2.4 -com.microsoft.azure:azure-keyvault-cryptography:jar:1.2.4 -com.microsoft.azure:azure-keyvault-webkey:jar:1.2.4 -com.microsoft.azure:azure-keyvault:jar:1.2.4 - diff --git a/doc/licenses/gson-2.8.0/README b/doc/licenses/gson-2.8.0/README deleted file mode 100644 index 40530bbf6..000000000 --- a/doc/licenses/gson-2.8.0/README +++ /dev/null @@ -1,8 +0,0 @@ -Gson (https://github.com/google/gson) -------------------------------------- - - Version: 2.8.0 - From: 'Google Inc.' (http://www.google.com/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/gson-2.8.0/dep-coordinates.txt b/doc/licenses/gson-2.8.0/dep-coordinates.txt deleted file mode 100644 index d171937b9..000000000 --- a/doc/licenses/gson-2.8.0/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.google.code.gson:gson:jar:2.8.0 diff --git a/doc/licenses/jackson-2.13.1/dep-coordinates.txt b/doc/licenses/jackson-2.13.1/dep-coordinates.txt index f469e53ff..f2cbd8d67 100644 --- a/doc/licenses/jackson-2.13.1/dep-coordinates.txt +++ b/doc/licenses/jackson-2.13.1/dep-coordinates.txt @@ -2,5 +2,4 @@ com.fasterxml.jackson.core:jackson-databind:jar:2.13.1 com.fasterxml.jackson.core:jackson-core:jar:2.13.1 com.fasterxml.jackson.core:jackson-annotations:jar:2.13.1 com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.13.1 -com.fasterxml.jackson.datatype:jackson-datatype-joda:jar:2.13.1 com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.13.1 diff --git a/doc/licenses/joda-time-2.10.8/NOTICE b/doc/licenses/joda-time-2.10.8/NOTICE deleted file mode 100644 index b8f54d7b2..000000000 --- a/doc/licenses/joda-time-2.10.8/NOTICE +++ /dev/null @@ -1,2 +0,0 @@ -This product includes software developed by -Joda.org (https://www.joda.org/). diff --git a/doc/licenses/joda-time-2.10.8/README b/doc/licenses/joda-time-2.10.8/README deleted file mode 100644 index ee8d28c96..000000000 --- a/doc/licenses/joda-time-2.10.8/README +++ /dev/null @@ -1,8 +0,0 @@ -Joda-Time (https://www.joda.org/joda-time/) ----------------------------------------------- - - Version: 2.10.8 - From: 'Joda.org' (https://www.joda.org/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/joda-time-2.10.8/dep-coordinates.txt b/doc/licenses/joda-time-2.10.8/dep-coordinates.txt deleted file mode 100644 index 0cc75bc83..000000000 --- a/doc/licenses/joda-time-2.10.8/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -joda-time:joda-time:jar:2.10.8 diff --git a/doc/licenses/json-smart-2.4.2/LICENSE b/doc/licenses/json-smart-2.4.2/LICENSE deleted file mode 100644 index 8f71f43fe..000000000 --- a/doc/licenses/json-smart-2.4.2/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/doc/licenses/json-smart-2.4.2/README b/doc/licenses/json-smart-2.4.2/README deleted file mode 100644 index 6ba0bb66a..000000000 --- a/doc/licenses/json-smart-2.4.2/README +++ /dev/null @@ -1,8 +0,0 @@ -json-smart (https://netplex.github.io/json-smart/) --------------------------------------------------- - - Version: 2.4.2 - From: 'Uriel Chemouni' (https://github.com/UrielCh) - License(s): - Apache v2.0 - diff --git a/doc/licenses/json-smart-2.4.2/dep-coordinates.txt b/doc/licenses/json-smart-2.4.2/dep-coordinates.txt deleted file mode 100644 index bf1c8b44b..000000000 --- a/doc/licenses/json-smart-2.4.2/dep-coordinates.txt +++ /dev/null @@ -1,2 +0,0 @@ -net.minidev:accessors-smart:jar:2.4.2 -net.minidev:json-smart:jar:2.4.2 diff --git a/doc/licenses/lang-tag-1.5/README b/doc/licenses/lang-tag-1.5/README deleted file mode 100644 index 2de3b0e74..000000000 --- a/doc/licenses/lang-tag-1.5/README +++ /dev/null @@ -1,8 +0,0 @@ -Nimbus Language Tags (https://bitbucket.org/connect2id/nimbus-language-tags) ----------------------------------------------------------------------------- - - Version: 1.5 - From: 'Connect2id Ltd.' (https://connect2id.com/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/lang-tag-1.5/dep-coordinates.txt b/doc/licenses/lang-tag-1.5/dep-coordinates.txt deleted file mode 100644 index fd885a12b..000000000 --- a/doc/licenses/lang-tag-1.5/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.nimbusds:lang-tag:jar:1.5 diff --git a/doc/licenses/nimbus-content-type-2.1/README b/doc/licenses/nimbus-content-type-2.1/README deleted file mode 100644 index 13b925e21..000000000 --- a/doc/licenses/nimbus-content-type-2.1/README +++ /dev/null @@ -1,8 +0,0 @@ -Nimbus Content Type (https://bitbucket.org/connect2id/nimbus-content-type) --------------------------------------------------------------------------- - - Version: 2.1 - From: 'Connect2id Ltd.' (https://connect2id.com/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/nimbus-content-type-2.1/dep-coordinates.txt b/doc/licenses/nimbus-content-type-2.1/dep-coordinates.txt deleted file mode 100644 index 8910f18d2..000000000 --- a/doc/licenses/nimbus-content-type-2.1/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.nimbusds:content-type:jar:2.1 diff --git a/doc/licenses/nimbus-jose-jwt-9.8.1/README b/doc/licenses/nimbus-jose-jwt-9.8.1/README deleted file mode 100644 index 5035f4127..000000000 --- a/doc/licenses/nimbus-jose-jwt-9.8.1/README +++ /dev/null @@ -1,8 +0,0 @@ -Nimbus JOSE+JWT (https://bitbucket.org/connect2id/nimbus-jose-jwt) ------------------------------------------------------------------- - - Version: 9.8.1 - From: 'Connect2id Ltd.' (https://connect2id.com/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/nimbus-jose-jwt-9.8.1/dep-coordinates.txt b/doc/licenses/nimbus-jose-jwt-9.8.1/dep-coordinates.txt deleted file mode 100644 index d15ff6d87..000000000 --- a/doc/licenses/nimbus-jose-jwt-9.8.1/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.nimbusds:nimbus-jose-jwt:jar:9.8.1 diff --git a/doc/licenses/oauth2-oidc-sdk-9.4/README b/doc/licenses/oauth2-oidc-sdk-9.4/README deleted file mode 100644 index f32808a0e..000000000 --- a/doc/licenses/oauth2-oidc-sdk-9.4/README +++ /dev/null @@ -1,9 +0,0 @@ -Nimbus OAuth 2.0 SDK with OpenID Connect extensions -(https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions) -------------------------------------------------------------------------------- - - Version: 9.4 - From: 'Connect2id Ltd.' (https://connect2id.com/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/oauth2-oidc-sdk-9.4/dep-coordinates.txt b/doc/licenses/oauth2-oidc-sdk-9.4/dep-coordinates.txt deleted file mode 100644 index 2bce0c919..000000000 --- a/doc/licenses/oauth2-oidc-sdk-9.4/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.nimbusds:oauth2-oidc-sdk:jar:9.4 diff --git a/doc/licenses/okhttp-3.14.7/README b/doc/licenses/okhttp-3.14.7/README deleted file mode 100644 index c3bd4173b..000000000 --- a/doc/licenses/okhttp-3.14.7/README +++ /dev/null @@ -1,8 +0,0 @@ -OkHttp (https://github.com/square/okhttp) ------------------------------------------ - - Version: 3.14.7 - From: 'Square, Inc.' (http://square.github.io/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/okhttp-3.14.7/dep-coordinates.txt b/doc/licenses/okhttp-3.14.7/dep-coordinates.txt deleted file mode 100644 index 9729cd760..000000000 --- a/doc/licenses/okhttp-3.14.7/dep-coordinates.txt +++ /dev/null @@ -1,3 +0,0 @@ -com.squareup.okhttp3:logging-interceptor:jar:3.14.7 -com.squareup.okhttp3:okhttp-urlconnection:jar:3.14.7 -com.squareup.okhttp3:okhttp:jar:3.14.7 diff --git a/doc/licenses/okio-1.17.2/README b/doc/licenses/okio-1.17.2/README deleted file mode 100644 index 13471ad46..000000000 --- a/doc/licenses/okio-1.17.2/README +++ /dev/null @@ -1,8 +0,0 @@ -Okio (https://github.com/square/okio) -------------------------------------- - - Version: 1.17.2 - From: 'Square, Inc.' (http://square.github.io/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/okio-1.17.2/dep-coordinates.txt b/doc/licenses/okio-1.17.2/dep-coordinates.txt deleted file mode 100644 index 54ab8297c..000000000 --- a/doc/licenses/okio-1.17.2/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.squareup.okio:okio:jar:1.17.2 diff --git a/doc/licenses/retrofit-2.7.2/README b/doc/licenses/retrofit-2.7.2/README deleted file mode 100644 index 83fea9db2..000000000 --- a/doc/licenses/retrofit-2.7.2/README +++ /dev/null @@ -1,8 +0,0 @@ -Retrofit (https://github.com/square/retrofit) ---------------------------------------------- - - Version: 2.7.2 - From: 'Square, Inc.' (http://square.github.io/) - License(s): - Apache v2.0 - diff --git a/doc/licenses/retrofit-2.7.2/dep-coordinates.txt b/doc/licenses/retrofit-2.7.2/dep-coordinates.txt deleted file mode 100644 index ff175a09d..000000000 --- a/doc/licenses/retrofit-2.7.2/dep-coordinates.txt +++ /dev/null @@ -1,3 +0,0 @@ -com.squareup.retrofit2:adapter-rxjava:jar:2.7.2 -com.squareup.retrofit2:converter-jackson:jar:2.7.2 -com.squareup.retrofit2:retrofit:jar:2.7.2 diff --git a/doc/licenses/rxjava-1.3.8/LICENSE b/doc/licenses/rxjava-1.3.8/LICENSE deleted file mode 100644 index 7f8ced0d1..000000000 --- a/doc/licenses/rxjava-1.3.8/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2012 Netflix, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/doc/licenses/rxjava-1.3.8/README b/doc/licenses/rxjava-1.3.8/README deleted file mode 100644 index 9aaa62d61..000000000 --- a/doc/licenses/rxjava-1.3.8/README +++ /dev/null @@ -1,8 +0,0 @@ -RxJava – Reactive Extensions for the JVM (https://github.com/ReactiveX/RxJava) ------------------------------------------------------------------------------- - - Version: 1.3.8 - From: 'RxJava Contributors' (https://github.com/ReactiveX/RxJava) - License(s): - Apache v2.0 - diff --git a/doc/licenses/rxjava-1.3.8/dep-coordinates.txt b/doc/licenses/rxjava-1.3.8/dep-coordinates.txt deleted file mode 100644 index e17a77560..000000000 --- a/doc/licenses/rxjava-1.3.8/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -io.reactivex:rxjava:jar:1.3.8 diff --git a/doc/licenses/stephenc-jcip-annotations-1.0-1/README b/doc/licenses/stephenc-jcip-annotations-1.0-1/README deleted file mode 100644 index 8e59938a1..000000000 --- a/doc/licenses/stephenc-jcip-annotations-1.0-1/README +++ /dev/null @@ -1,8 +0,0 @@ -Clean-room JCIP Annotations (https://github.com/stephenc/jcip-annotations) --------------------------------------------------------------------------- - - Version: 1.0-1 - From: 'Stephen Connolly' (https://github.com/stephenc) - License(s): - Apache v2.0 - diff --git a/doc/licenses/stephenc-jcip-annotations-1.0-1/dep-coordinates.txt b/doc/licenses/stephenc-jcip-annotations-1.0-1/dep-coordinates.txt deleted file mode 100644 index e42206c27..000000000 --- a/doc/licenses/stephenc-jcip-annotations-1.0-1/dep-coordinates.txt +++ /dev/null @@ -1 +0,0 @@ -com.github.stephenc.jcip:jcip-annotations:jar:1.0-1 diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/.ratignore b/extensions/guacamole-vault/modules/guacamole-vault-azure/.ratignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/pom.xml b/extensions/guacamole-vault/modules/guacamole-vault-azure/pom.xml deleted file mode 100644 index f2448a3d6..000000000 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/pom.xml +++ /dev/null @@ -1,194 +0,0 @@ - - - - - 4.0.0 - org.apache.guacamole - guacamole-vault-azure - jar - 1.4.0 - guacamole-vault-azure - http://guacamole.apache.org/ - - - 1.7.4 - 3.14.7 - - - - org.apache.guacamole - guacamole-vault - 1.4.0 - ../../ - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - false - - - - - - - - - - - org.apache.guacamole - guacamole-ext - provided - - - - - org.apache.guacamole - guacamole-vault-base - 1.4.0 - - - - - com.microsoft.azure - azure-keyvault - 1.2.4 - - - - - org.slf4j - slf4j-api - - - - - com.microsoft.azure - azure-client-runtime - - - commons-codec - commons-codec - - - - - - - - - com.microsoft.azure - adal4j - 1.6.7 - - - - - org.slf4j - slf4j-api - - - - - org.apache.commons - commons-lang3 - - - - - - - - com.microsoft.azure - azure-client-runtime - ${azure-client-runtimes.version} - - - com.microsoft.rest - client-runtime - ${azure-client-runtimes.version} - - - org.apache.commons - commons-lang3 - - - com.squareup.okhttp3 - okhttp - - - com.squareup.okhttp3 - okhttp-urlconnection - - - com.squareup.okhttp3 - logging-interceptor - - - - - - - org.apache.commons - commons-lang3 - 3.8.1 - - - commons-codec - commons-codec - 1.14 - - - - - com.squareup.okhttp3 - okhttp - ${okhttp.version} - - - com.squareup.okhttp3 - okhttp-urlconnection - ${okhttp.version} - - - com.squareup.okhttp3 - logging-interceptor - ${okhttp.version} - - - - - diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProvider.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProvider.java deleted file mode 100644 index f5f367e09..000000000 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProvider.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.vault.azure; - -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.vault.VaultAuthenticationProvider; - -/** - * VaultAuthenticationProvider implementation which reads secrets from Azure - * Key Vault. - */ -public class AzureKeyVaultAuthenticationProvider extends VaultAuthenticationProvider { - - /** - * Creates a new AzureKeyVaultAuthenticationProvider which reads secrets - * from a configured Azure Key Vault. - * - * @throws GuacamoleException - * If configuration details cannot be read from guacamole.properties. - */ - public AzureKeyVaultAuthenticationProvider() throws GuacamoleException { - super(new AzureKeyVaultAuthenticationProviderModule()); - } - - @Override - public String getIdentifier() { - return "azure-keyvault"; - } - -} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProviderModule.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProviderModule.java deleted file mode 100644 index a044743ab..000000000 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/AzureKeyVaultAuthenticationProviderModule.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.vault.azure; - -import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.vault.VaultAuthenticationProviderModule; -import org.apache.guacamole.vault.azure.conf.AzureKeyVaultConfigurationService; -import org.apache.guacamole.vault.azure.conf.AzureKeyVaultCredentials; -import org.apache.guacamole.vault.azure.secret.AzureKeyVaultSecretService; -import org.apache.guacamole.vault.conf.VaultConfigurationService; -import org.apache.guacamole.vault.secret.VaultSecretService; - -/** - * Guice module which configures injections specific to Azure Key Vault - * support. - */ -public class AzureKeyVaultAuthenticationProviderModule - extends VaultAuthenticationProviderModule { - - /** - * Creates a new AzureKeyVaultAuthenticationProviderModule which - * configures dependency injection for the Azure Key Vault authentication - * provider and related services. - * - * @throws GuacamoleException - * If configuration details in guacamole.properties cannot be parsed. - */ - public AzureKeyVaultAuthenticationProviderModule() throws GuacamoleException {} - - @Override - protected void configureVault() { - - // Bind services specific to Azure Key Vault - bind(VaultConfigurationService.class).to(AzureKeyVaultConfigurationService.class); - bind(VaultSecretService.class).to(AzureKeyVaultSecretService.class); - - // Bind ADAL credentials implementation required for authenticating - // against Azure - bind(KeyVaultCredentials.class).to(AzureKeyVaultCredentials.class); - - } - -} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultAuthenticationException.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultAuthenticationException.java deleted file mode 100644 index d1eba9be7..000000000 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultAuthenticationException.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.vault.azure.conf; - -/** - * Unchecked exception thrown by AzureKeyVaultCredentials if an error occurs - * during the authentication process. Note that the base KeyVaultCredentials - * base class does not provide for checked exceptions within the authentication - * process. - * - * @see AzureKeyVaultCredentials#doAuthenticate(java.lang.String, java.lang.String, java.lang.String) - */ -public class AzureKeyVaultAuthenticationException extends RuntimeException { - - /** - * Creates a new AzureKeyVaultAuthenticationException having the given - * human-readable message. - * - * @param message - * A human-readable message describing the error that occurred. - */ - public AzureKeyVaultAuthenticationException(String message) { - super(message); - } - - /** - * Creates a new AzureKeyVaultAuthenticationException having the given - * human-readable message and cause. - * - * @param message - * A human-readable message describing the error that occurred. - * - * @param cause - * The error that caused this exception. - */ - public AzureKeyVaultAuthenticationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java deleted file mode 100644 index 16620c2f6..000000000 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultConfigurationService.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.vault.azure.conf; - -import com.google.inject.Inject; -import com.google.inject.Singleton; -import com.microsoft.aad.adal4j.ClientCredential; -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.environment.Environment; -import org.apache.guacamole.properties.IntegerGuacamoleProperty; -import org.apache.guacamole.properties.StringGuacamoleProperty; -import org.apache.guacamole.vault.conf.VaultConfigurationService; - -/** - * Service for retrieving configuration information regarding the Azure Key - * Vault authentication extension. - */ -@Singleton -public class AzureKeyVaultConfigurationService extends VaultConfigurationService { - - /** - * The Guacamole server environment. - */ - @Inject - private Environment environment; - - /** - * The name of the file which contains the YAML mapping of connection - * parameter token to Azure Key Vault secret name. - */ - private static final String TOKEN_MAPPING_FILENAME = "azure-keyvault-token-mapping.yml"; - - /** - * The name of the properties file containing Guacamole configuration - * properties whose values are the names of corresponding secrets within - * Azure Key Vault. - */ - private static final String PROPERTIES_FILENAME = "guacamole.properties.azure"; - - /** - * The number of milliseconds that each retrieved secret should be cached - * for. - */ - private static final IntegerGuacamoleProperty SECRET_TTL = new IntegerGuacamoleProperty() { - - @Override - public String getName() { - return "azure-keyvault-secret-ttl"; - } - - }; - - /** - * The URL of the Azure Key Vault that should be used to populate token - * values. - */ - private static final StringGuacamoleProperty VAULT_URL = new StringGuacamoleProperty() { - - @Override - public String getName() { - return "azure-keyvault-url"; - } - - }; - - /** - * The client ID that should be used to authenticate with Azure Key Vault - * using ADAL. - */ - private static final StringGuacamoleProperty CLIENT_ID = new StringGuacamoleProperty() { - - @Override - public String getName() { - return "azure-keyvault-client-id"; - } - - }; - - /** - * The client key that should be used to authenticate with Azure Key Vault - * using ADAL. - */ - private static final StringGuacamoleProperty CLIENT_KEY = new StringGuacamoleProperty() { - - @Override - public String getName() { - return "azure-keyvault-client-key"; - } - - }; - - /** - * Creates a new AzureKeyVaultConfigurationService which reads the token - * mapping from "azure-keyvault-token-mapping.yml" and properties from - * "guacamole.properties.azure". The token mapping is a YAML file which - * lists each connection parameter token and the name of the secret from - * which the value for that token should be read, while the properties - * file is an alternative to guacamole.properties where each property - * value is the name of a secret containing the actual value. - */ - public AzureKeyVaultConfigurationService() { - super(TOKEN_MAPPING_FILENAME, PROPERTIES_FILENAME); - } - - /** - * Returns the number of milliseconds that each retrieved secret should be - * cached for. By default, secrets are cached for 10 seconds. - * - * @return - * The number of milliseconds to cache each retrieved secret. - * - * @throws GuacamoleException - * If the value specified within guacamole.properties cannot be - * parsed. - */ - public int getSecretTTL() throws GuacamoleException { - return environment.getProperty(SECRET_TTL, 10000); - } - - /** - * Returns the base URL of the Azure Key Vault containing the secrets that - * should be retrieved to populate connection parameter tokens. The base - * URL is specified with the "azure-keyvault-url" property. - * - * @return - * The base URL of the Azure Key Vault. - * - * @throws GuacamoleException - * If the base URL is not specified within guacamole.properties. - */ - public String getVaultURL() throws GuacamoleException { - return environment.getRequiredProperty(VAULT_URL); - } - - /** - * Returns the credentials that should be used to authenticate with Azure - * Key Vault when retrieving secrets. Azure's "ADAL" authentication will be - * used, requiring a client ID and key. These values are specified with the - * "azure-keyvault-client-id" and "azure-keyvault-client-key" properties - * respectively. - * - * @return - * The credentials that should be used to authenticate with Azure Key - * Vault. - * - * @throws GuacamoleException - * If the client ID or key are not specified within - * guacamole.properties. - */ - public ClientCredential getClientCredentials() throws GuacamoleException { - return new ClientCredential( - environment.getRequiredProperty(CLIENT_ID), - environment.getRequiredProperty(CLIENT_KEY) - ); - } - -} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultCredentials.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultCredentials.java deleted file mode 100644 index 36bde5d53..000000000 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/conf/AzureKeyVaultCredentials.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.vault.azure.conf; - -import com.google.inject.Inject; -import com.microsoft.aad.adal4j.AuthenticationContext; -import com.microsoft.aad.adal4j.AuthenticationResult; -import com.microsoft.aad.adal4j.ClientCredential; -import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; -import java.net.MalformedURLException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import org.apache.guacamole.GuacamoleException; - -/** - * KeyVaultCredentials implementation which retrieves the required client ID - * and key from guacamole.properties. Note that KeyVaultCredentials as - * implemented in the Azure Java SDK is NOT THREADSAFE; it leverages a - * non-concurrent HashMap for authentication result caching and does not - * perform any synchronization. - */ -public class AzureKeyVaultCredentials extends KeyVaultCredentials { - - /** - * Service for retrieving configuration information. - */ - @Inject - private AzureKeyVaultConfigurationService confService; - - /** - * {@inheritDoc} - * - * @throws AzureKeyVaultAuthenticationException - * If an error occurs preventing successful authentication. Note that - * this exception is unchecked. Uses of this class which need to be - * aware of errors in the authentication process must manually catch - * this exception. - */ - @Override - public String doAuthenticate(String authorization, String resource, - String scope) throws AzureKeyVaultAuthenticationException { - - // Read Azure credentials from guacamole.properties - ClientCredential credentials; - try { - credentials = confService.getClientCredentials(); - } - catch (GuacamoleException e) { - throw new AzureKeyVaultAuthenticationException("Azure " - + "credentials could not be read.", e); - } - - ExecutorService service = Executors.newFixedThreadPool(1); - try { - - // Attempt to aquire authentication token from Azure - AuthenticationContext context = new AuthenticationContext(authorization, false, service); - Future future = context.acquireToken(resource, credentials, null); - - // Wait for response - AuthenticationResult result = future.get(); - - // The semantics of a null return value are not documented, however - // example code provided with the Azure Java SDK demonstrates that - // a null check is required, albeit without explanation - if (result == null) - throw new AzureKeyVaultAuthenticationException( - "Authentication result from Azure was empty."); - - // Return authentication token from successful response - return result.getAccessToken(); - - } - - // Rethrow any errors which occur during the authentication process as - // AzureKeyVaultAuthenticationExceptions - catch (MalformedURLException e) { - throw new AzureKeyVaultAuthenticationException("Azure " - + "authentication URL is malformed.", e); - } - catch (InterruptedException e) { - throw new AzureKeyVaultAuthenticationException("Azure " - + "authentication process was interrupted.", e); - } - catch (ExecutionException e) { - throw new AzureKeyVaultAuthenticationException("Authentication " - + "against Azure failed.", e); - } - - finally { - service.shutdown(); - } - - } - -} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java deleted file mode 100644 index 401a44607..000000000 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/java/org/apache/guacamole/vault/azure/secret/AzureKeyVaultSecretService.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.vault.azure.secret; - -import com.google.inject.Inject; -import com.google.inject.Provider; -import com.google.inject.Singleton; -import com.microsoft.azure.keyvault.KeyVaultClient; -import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; -import com.microsoft.azure.keyvault.models.SecretBundle; -import com.microsoft.rest.ServiceCallback; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.protocol.GuacamoleConfiguration; -import org.apache.guacamole.token.TokenFilter; -import org.apache.guacamole.vault.azure.conf.AzureKeyVaultAuthenticationException; -import org.apache.guacamole.vault.azure.conf.AzureKeyVaultConfigurationService; -import org.apache.guacamole.vault.secret.CachedVaultSecretService; - -/** - * Service which retrieves secrets from Azure Key Vault. - */ -@Singleton -public class AzureKeyVaultSecretService extends CachedVaultSecretService { - - /** - * Pattern which matches contiguous groups of characters which are not - * allowed within Azure Key Vault secret names. - */ - private static final Pattern DISALLOWED_CHARACTERS = Pattern.compile("[^a-zA-Z0-9-]+"); - - /** - * Service for retrieving configuration information. - */ - @Inject - private AzureKeyVaultConfigurationService confService; - - /** - * Provider for Azure Key Vault credentials. - */ - @Inject - private Provider credentialProvider; - - /** - * {@inheritDoc} - * - *

Azure Key Vault allows strictly a-z, A-Z, 0-9, and "-". This - * implementation strips out all contiguous groups of characters which are - * not allowed by Azure Key Vault, replacing them with a single dash. - */ - @Override - public String canonicalize(String nameComponent) { - Matcher disallowed = DISALLOWED_CHARACTERS.matcher(nameComponent); - return disallowed.replaceAll("-"); - } - - @Override - protected CachedSecret refreshCachedSecret(String name) - throws GuacamoleException { - - int ttl = confService.getSecretTTL(); - String url = confService.getVaultURL(); - - CompletableFuture retrievedValue = new CompletableFuture<>(); - - // getSecretAsync() still blocks for around half a second, despite - // technically being asynchronous - (new Thread() { - - @Override - public void run() { - try { - - // Retrieve requested secret from Azure Key Vault - KeyVaultClient client = new KeyVaultClient(credentialProvider.get()); - client.getSecretAsync(url, name, new ServiceCallback() { - - @Override - public void failure(Throwable t) { - retrievedValue.completeExceptionally(t); - } - - @Override - public void success(SecretBundle secret) { - String value = (secret != null) ? secret.value() : null; - retrievedValue.complete(value); - } - - }); - - } - catch (AzureKeyVaultAuthenticationException e) { - retrievedValue.completeExceptionally(e); - } - } - - }).start(); - - // Cache retrieved value - return new CachedSecret(retrievedValue, ttl); - - } - - @Override - public Map> getTokens(GuacamoleConfiguration config, - TokenFilter filter) throws GuacamoleException { - return Collections.emptyMap(); - } - -} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/resources/guac-manifest.json b/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/resources/guac-manifest.json deleted file mode 100644 index 9f6ee94a4..000000000 --- a/extensions/guacamole-vault/modules/guacamole-vault-azure/src/main/resources/guac-manifest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - - "guacamoleVersion" : "1.4.0", - - "name" : "Azure Key Vault", - "namespace" : "azure-keyvault", - - "authProviders" : [ - "org.apache.guacamole.vault.azure.AzureKeyVaultAuthenticationProvider" - ], - - "translations" : [ - "translations/en.json" - ] - -} diff --git a/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml b/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml index a5042dfb8..1b2cb5996 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-dist/pom.xml @@ -42,13 +42,6 @@ - - - org.apache.guacamole - guacamole-vault-azure - 1.4.0 - - org.apache.guacamole diff --git a/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml b/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml index 30c93f41a..6f41e5278 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml +++ b/extensions/guacamole-vault/modules/guacamole-vault-dist/src/main/assembly/dist.xml @@ -33,14 +33,6 @@ - - - azure - - org.apache.guacamole:guacamole-vault-azure - - - ksm diff --git a/extensions/guacamole-vault/pom.xml b/extensions/guacamole-vault/pom.xml index 80eb3da57..0933f08fe 100644 --- a/extensions/guacamole-vault/pom.xml +++ b/extensions/guacamole-vault/pom.xml @@ -46,7 +46,6 @@ modules/guacamole-vault-base - modules/guacamole-vault-azure modules/guacamole-vault-ksm diff --git a/pom.xml b/pom.xml index 2b9b1641f..2dd057056 100644 --- a/pom.xml +++ b/pom.xml @@ -382,11 +382,6 @@ jackson-dataformat-yaml ${jackson.version} - - com.fasterxml.jackson.datatype - jackson-datatype-joda - ${jackson.version} - com.fasterxml.jackson.module jackson-module-jaxb-annotations From 96c8c7de615b01fb4d91239e68273dbbce05c255 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 25 Jan 2022 19:50:17 -0800 Subject: [PATCH 36/36] GUACAMOLE-641: Correct old references to the temporarily-removed Azure support. --- .../guacamole/vault/VaultAuthenticationProviderModule.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProviderModule.java b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProviderModule.java index 94119a7a2..a790d0119 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProviderModule.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-base/src/main/java/org/apache/guacamole/vault/VaultAuthenticationProviderModule.java @@ -33,7 +33,7 @@ import org.apache.guacamole.vault.user.VaultUserContextFactory; * key vaults. When adding support for a key vault provider, a subclass * specific to that vault implementation will need to be created. * - * @see AzureKeyVaultAuthenticationProviderModule + * @see KsmAuthenticationProviderModule */ public abstract class VaultAuthenticationProviderModule extends AbstractModule { @@ -44,7 +44,8 @@ public abstract class VaultAuthenticationProviderModule extends AbstractModule { /** * Creates a new VaultAuthenticationProviderModule which configures - * dependency injection for the Azure Key Vault authentication provider. + * dependency injection for the authentication provider of a vault + * implementation. * * @throws GuacamoleException * If an error occurs while retrieving the Guacamole server @@ -63,7 +64,7 @@ public abstract class VaultAuthenticationProviderModule extends AbstractModule { * - VaultConfigurationService * - VaultSecretService * - * @see AzureKeyVaultAuthenticationProviderModule + * @see KsmAuthenticationProviderModule */ protected abstract void configureVault();