Mercurial > hg > thermostat-ng > agent
changeset 2755:d717e6133812
Add Keycloak authentication support in commands plugin.
Reviewed-by: jkang
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/024887.html
author | Severin Gehwolf <sgehwolf@redhat.com> |
---|---|
date | Thu, 07 Sep 2017 18:56:23 +0200 |
parents | 6018bc5f92b3 |
children | d844cfb19af1 |
files | agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentStartupConfiguration.java agent/core/src/main/java/com/redhat/thermostat/agent/config/AuthenticationProviderConfig.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/AgentApplication.java plugins/commands/agent/src/main/java/com/redhat/thermostat/commands/agent/internal/CommandsBackend.java plugins/commands/agent/src/test/java/com/redhat/thermostat/commands/agent/internal/CommandsBackendTest.java |
diffstat | 5 files changed, 139 insertions(+), 33 deletions(-) [+] |
line wrap: on
line diff
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentStartupConfiguration.java Tue Sep 12 12:19:00 2017 +0200 +++ b/agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentStartupConfiguration.java Thu Sep 07 18:56:23 2017 +0200 @@ -36,7 +36,7 @@ package com.redhat.thermostat.agent.config; -public class AgentStartupConfiguration { +public class AgentStartupConfiguration implements AuthenticationProviderConfig { private boolean purge; private long startTime; @@ -46,15 +46,15 @@ private String keycloakRealm; private String keycloakClient; private boolean basicAuthEnabled; - + AgentStartupConfiguration() { } - + // TODO: that should be a friend, we only want the Service to set this value public void setStartTime(long startTime) { this.startTime = startTime; } - + public long getStartTime() { return startTime; } @@ -62,7 +62,7 @@ void setPurge(boolean purge) { this.purge = purge; } - + public boolean purge() { return purge; } @@ -83,6 +83,7 @@ this.keycloakClient = keycloakClient; } + @Override public boolean isKeycloakEnabled() { return keycloakEnabled; } @@ -99,6 +100,7 @@ this.keycloakRealm = keycloakRealm; } + @Override public boolean isBasicAuthEnabled() { return basicAuthEnabled; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/main/java/com/redhat/thermostat/agent/config/AuthenticationProviderConfig.java Thu Sep 07 18:56:23 2017 +0200 @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.agent.config; + +import com.redhat.thermostat.annotations.Service; + +/** + * + * Configuration service informing plugins which authentication + * scheme is to be used. + * + */ +@Service +public interface AuthenticationProviderConfig { + + /** + * + * @return {@code true} if and only if Keycloak is being used as the + * authentication provider. + */ + boolean isKeycloakEnabled(); + + /** + * + * @return {@code true} if basic authentication should be used. If + * {@link AuthenticationProviderConfig#isKeycloakEnabled()} also + * returns true Keycloak authentication should be preferred. + */ + boolean isBasicAuthEnabled(); + +}
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/internal/AgentApplication.java Tue Sep 12 12:19:00 2017 +0200 +++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/AgentApplication.java Thu Sep 07 18:56:23 2017 +0200 @@ -40,9 +40,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import com.redhat.thermostat.agent.dao.AgentInfoDAO; -import com.redhat.thermostat.agent.dao.BackendInfoDAO; - import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; @@ -54,6 +51,9 @@ import com.redhat.thermostat.agent.config.AgentConfigsUtils; import com.redhat.thermostat.agent.config.AgentOptionParser; import com.redhat.thermostat.agent.config.AgentStartupConfiguration; +import com.redhat.thermostat.agent.config.AuthenticationProviderConfig; +import com.redhat.thermostat.agent.dao.AgentInfoDAO; +import com.redhat.thermostat.agent.dao.BackendInfoDAO; import com.redhat.thermostat.backend.BackendRegistry; import com.redhat.thermostat.backend.BackendService; import com.redhat.thermostat.common.ExitStatus; @@ -70,6 +70,7 @@ import com.redhat.thermostat.shared.config.CommonPaths; import com.redhat.thermostat.shared.config.InvalidConfigurationException; import com.redhat.thermostat.storage.core.WriterID; + import sun.misc.Signal; import sun.misc.SignalHandler; @@ -110,7 +111,7 @@ private BackendInfoDAO backendInfoDAO; @Reference private CommonPaths commonPaths; - + private CommandRegistry reg; private CountDownLatch shutdownLatch; @@ -174,6 +175,7 @@ @Override public void run(CommandContext ctx) throws CommandException { configuration = configurationCreator.create(commonPaths); + context.registerService(AuthenticationProviderConfig.class, configuration, null); parseArguments(ctx.getArguments()); if (!parser.isHelp()) {
--- a/plugins/commands/agent/src/main/java/com/redhat/thermostat/commands/agent/internal/CommandsBackend.java Tue Sep 12 12:19:00 2017 +0200 +++ b/plugins/commands/agent/src/main/java/com/redhat/thermostat/commands/agent/internal/CommandsBackend.java Thu Sep 07 18:56:23 2017 +0200 @@ -52,6 +52,10 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.osgi.framework.BundleContext; +import com.redhat.thermostat.agent.config.AuthenticationProviderConfig; +import com.redhat.thermostat.agent.http.RequestFailedException; +import com.redhat.thermostat.agent.keycloak.KeycloakAccessToken; +import com.redhat.thermostat.agent.keycloak.KeycloakAccessTokenService; import com.redhat.thermostat.backend.Backend; import com.redhat.thermostat.backend.BaseBackend; import com.redhat.thermostat.commands.agent.internal.socket.AgentSocketOnMessageCallback; @@ -93,7 +97,7 @@ @Reference private SystemID systemId; - + @Reference private WriterID agentId; @@ -102,14 +106,20 @@ @Reference private ConfigurationInfoSource commandInfo; - + @Reference private SSLConfiguration sslConfig; + @Reference + private KeycloakAccessTokenService keycloakTokenService; + + @Reference + private AuthenticationProviderConfig authConfig; + public CommandsBackend() { this(new WsClientCreator(), new CredentialsCreator(), new ConfigCreator(), new CountDownLatch(1)); } - + // For testing purposes CommandsBackend(WsClientCreator creator, CredentialsCreator credsCreator, @@ -181,7 +191,7 @@ } /** - * + * * @return {@code true} if and only if connection was successfully made */ private boolean connectWsClient() { @@ -189,15 +199,22 @@ try { URI microserviceURI = config.getGatewayURL(); String agent = agentId.getWriterID(); - String sysId = systemId.getSystemID(); + String sysId = systemId.getSystemID(); String cmdUriPath = String.format(ENDPOINT_FORMAT, sysId, agent); URI agentUri = microserviceURI.resolve(cmdUriPath); AgentSocketOnMessageCallback onMsgCallback = new AgentSocketOnMessageCallback(receiverReg); CmdChannelAgentSocket agentSocket = new CmdChannelAgentSocket( onMsgCallback, socketConnectLatch, agent); ClientUpgradeRequest agentRequest = new ClientUpgradeRequest(); - agentRequest.setHeader(HttpHeader.AUTHORIZATION.asString(), - getBasicAuthHeaderValue()); + if (authConfig.isKeycloakEnabled()) { + agentRequest.setHeader(HttpHeader.AUTHORIZATION.asString(), + getKeycloakAuthHeaderValue()); + } else if (authConfig.isBasicAuthEnabled()) { + agentRequest.setHeader(HttpHeader.AUTHORIZATION.asString(), + getBasicAuthHeaderValue()); + } else { + logger.warning("No authentication scheme configured. Connection will likely fail."); + } wsClient.connect(agentSocket, agentUri, agentRequest); logger.fine("WebSocket connect initiated."); expired = !socketConnectLatch.await(10, TimeUnit.SECONDS); @@ -215,6 +232,17 @@ } } + private String getKeycloakAuthHeaderValue() { + String bearerToken = UNKNOWN_CREDS; + try { + KeycloakAccessToken keycloakToken = keycloakTokenService.getAccessToken(); + bearerToken = keycloakToken.getAccessToken(); + } catch (RequestFailedException e) { + logger.warning("Failed to get keycloak access token from auth provider."); + } + return "Bearer " + bearerToken; + } + String getBasicAuthHeaderValue() { String username = creds.getUsername(); char[] pwdChar = creds.getPassword(); @@ -226,40 +254,45 @@ String pwd = new String(pwdChar); userpassword = username + ":" + pwd; } - + @SuppressWarnings("restriction") String encodedAuthorization = new sun.misc.BASE64Encoder() .encode(userpassword.getBytes()); return "Basic " + encodedAuthorization; } - + // DS bind method protected void bindPaths(CommonPaths paths) { this.paths = paths; } - + // DS bind method protected void bindAgentId(WriterID agentId) { this.agentId = agentId; } - + // DS bind method protected void bindSystemId(SystemID systemId) { this.systemId = systemId; } - + + // DS bind method + protected void bindAuthConfig(AuthenticationProviderConfig authConfig) { + this.authConfig = authConfig; + } + static class WsClientCreator { WebSocketClientFacade createClient(SSLConfiguration sslConfig) { return new WebSocketClientFacadeImpl(sslConfig); } } - + static class CredentialsCreator { StorageCredentials create(CommonPaths paths) { return new FileStorageCredentials(paths.getUserAgentAuthConfigFile()); } } - + static class ConfigCreator { PluginConfiguration createConfig(ConfigurationInfoSource source) { return new PluginConfiguration(source, PLUGIN_ID);
--- a/plugins/commands/agent/src/test/java/com/redhat/thermostat/commands/agent/internal/CommandsBackendTest.java Tue Sep 12 12:19:00 2017 +0200 +++ b/plugins/commands/agent/src/test/java/com/redhat/thermostat/commands/agent/internal/CommandsBackendTest.java Thu Sep 07 18:56:23 2017 +0200 @@ -58,6 +58,7 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.Version; +import com.redhat.thermostat.agent.config.AuthenticationProviderConfig; import com.redhat.thermostat.commands.agent.internal.CommandsBackend.ConfigCreator; import com.redhat.thermostat.commands.agent.internal.CommandsBackend.CredentialsCreator; import com.redhat.thermostat.commands.agent.internal.CommandsBackend.WsClientCreator; @@ -80,7 +81,7 @@ private BundleContext bundleContext; private WebSocketClientFacade client; private CountDownLatch socketConnect; - + @Before public void setup() throws IOException { socketConnect = new CountDownLatch(1); @@ -107,25 +108,28 @@ when(sId.getSystemID()).thenReturn(SYSTEM_ID); backend.bindAgentId(wId); backend.bindSystemId(sId); + AuthenticationProviderConfig authConfig = mock(AuthenticationProviderConfig.class); + when(authConfig.isBasicAuthEnabled()).thenReturn(true); + backend.bindAuthConfig(authConfig); } - + @Test public void testGetBasicAuthHeaderValueNoCreds() { doUnknownCredsTest(backend); } - + @Test public void testGetBasicAuthHeaderValueWithNoUsername() { when(creds.getPassword()).thenReturn(new char[] { 'a', 'b', 'c' }); doUnknownCredsTest(backend); } - + @Test public void testGetBasicAuthHeaderValueWithNoPassword() { when(creds.getUsername()).thenReturn("foo-user"); doUnknownCredsTest(backend); } - + @Test public void canGetBasicAuthHeaderValueWithUsernamePwd() { String password = "foo"; @@ -135,13 +139,13 @@ String expected = base64EncodedHeader(username + ":" + password); assertEquals(expected, backend.getBasicAuthHeaderValue()); } - + @Test public void testComponentActivated() { // setup invokes it. only do verification here verify(client).start(); } - + @Test public void testActivateSuccess() throws IOException { String password = "foo"; @@ -164,17 +168,17 @@ assertEquals(expectedHeader, actualHeader); assertTrue("Expected backend to be active", backend.isActive()); } - + @Test public void testActivateFail() throws IOException { // set up for failure doThrow(IOException.class).when(client).connect(any(CmdChannelAgentSocket.class), any(URI.class), any(ClientUpgradeRequest.class)); - + boolean success = backend.activate(); assertFalse("Expected unsuccessful activation", success); assertFalse(backend.isActive()); } - + @Test public void testDeactivate() throws IOException { // release connect latch