mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-641: Add initial draft implementation of KSM vault support for Guacamole.
This commit is contained in:
20
doc/licenses/bouncycastle-fips-1.0.2.1/LICENSE
Normal file
20
doc/licenses/bouncycastle-fips-1.0.2.1/LICENSE
Normal file
@@ -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.
|
8
doc/licenses/bouncycastle-fips-1.0.2.1/README
Normal file
8
doc/licenses/bouncycastle-fips-1.0.2.1/README
Normal file
@@ -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)
|
||||||
|
|
@@ -0,0 +1 @@
|
|||||||
|
org.bouncycastle:bc-fips:jar:1.0.2.1
|
9
doc/licenses/jetbrains-annotations-13.0/README
Normal file
9
doc/licenses/jetbrains-annotations-13.0/README
Normal file
@@ -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
|
||||||
|
|
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains:annotations:jar:13.0
|
2
doc/licenses/kotlin-1.5.30/NOTICE.txt
Normal file
2
doc/licenses/kotlin-1.5.30/NOTICE.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Kotlin Compiler
|
||||||
|
Copyright 2010-2020 JetBrains s.r.o and respective authors and developers
|
8
doc/licenses/kotlin-1.5.30/README
Normal file
8
doc/licenses/kotlin-1.5.30/README
Normal file
@@ -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
|
||||||
|
|
5
doc/licenses/kotlin-1.5.30/dep-coordinates.txt
Normal file
5
doc/licenses/kotlin-1.5.30/dep-coordinates.txt
Normal file
@@ -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
|
2
doc/licenses/kotlinx-serialization-1.2.1/NOTICE.txt
Normal file
2
doc/licenses/kotlinx-serialization-1.2.1/NOTICE.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
kotlinx.serialization library.
|
||||||
|
Copyright 2017-2019 JetBrains s.r.o and respective authors and developers
|
8
doc/licenses/kotlinx-serialization-1.2.1/README
Normal file
8
doc/licenses/kotlinx-serialization-1.2.1/README
Normal file
@@ -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
|
||||||
|
|
@@ -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
|
21
doc/licenses/ksm-sdk-16.2.1/LICENSE
Normal file
21
doc/licenses/ksm-sdk-16.2.1/LICENSE
Normal file
@@ -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.
|
9
doc/licenses/ksm-sdk-16.2.1/README
Normal file
9
doc/licenses/ksm-sdk-16.2.1/README
Normal file
@@ -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)
|
||||||
|
|
1
doc/licenses/ksm-sdk-16.2.1/dep-coordinates.txt
Normal file
1
doc/licenses/ksm-sdk-16.2.1/dep-coordinates.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
com.keepersecurity.secrets-manager:core:jar:16.2.1
|
@@ -0,0 +1,98 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||||
|
http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-vault-ksm</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<name>guacamole-vault-ksm</name>
|
||||||
|
<url>http://guacamole.apache.org/</url>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-vault</artifactId>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<relativePath>../../</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<kotlin.version>1.5.30</kotlin.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- Guacamole Extension API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-ext</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Guacamole base key vault support -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-vault-base</artifactId>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.keepersecurity.secrets-manager</groupId>
|
||||||
|
<artifactId>core</artifactId>
|
||||||
|
<version>16.2.1</version>
|
||||||
|
|
||||||
|
<!-- Correct version conflict (different versions across transitive
|
||||||
|
dependencies) -->
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-common</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Use same version of Kotlin across all dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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<KeyValueStorage> {
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
@@ -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<String, KeeperRecord> 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<String, KeeperRecord> 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<String> 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<String, KeeperRecord> 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<String> 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<KeeperRecord> 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<KeeperRecordField> 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<KeeperRecord> 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<String> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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<String> getValue(String name) throws GuacamoleException {
|
||||||
|
return ksm.getSecret(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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"
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
@@ -47,6 +47,7 @@
|
|||||||
|
|
||||||
<!-- Provider-specific implementations -->
|
<!-- Provider-specific implementations -->
|
||||||
<module>modules/guacamole-vault-azure</module>
|
<module>modules/guacamole-vault-azure</module>
|
||||||
|
<module>modules/guacamole-vault-ksm</module>
|
||||||
|
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user