Mercurial > hg > thermostat-ng > agent
view plugins/commands/agent/src/main/java/com/redhat/thermostat/commands/agent/internal/CommandsBackend.java @ 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 | 406fe8f9d8bf |
children | 3f1f63227847 |
line wrap: on
line source
/* * 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.commands.agent.internal; import java.io.IOException; import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.eclipse.jetty.http.HttpHeader; 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; import com.redhat.thermostat.commands.agent.internal.socket.CmdChannelAgentSocket; import com.redhat.thermostat.commands.agent.receiver.ReceiverRegistry; import com.redhat.thermostat.common.Ordered; import com.redhat.thermostat.common.Version; import com.redhat.thermostat.common.config.experimental.ConfigurationInfoSource; import com.redhat.thermostat.common.plugin.PluginConfiguration; import com.redhat.thermostat.common.plugin.SystemID; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.shared.config.CommonPaths; import com.redhat.thermostat.shared.config.SSLConfiguration; import com.redhat.thermostat.storage.config.FileStorageCredentials; import com.redhat.thermostat.storage.core.StorageCredentials; import com.redhat.thermostat.storage.core.WriterID; @Component @Service(value = Backend.class) public class CommandsBackend extends BaseBackend { private static final Logger logger = LoggingUtils.getLogger(CommandsBackend.class); private static final String NAME = "Commands (cmd-channel) Backend"; private static final String DESCRIPTION = "Establishes web-socket connections to the microservice endpoint so as to be able to receive command requests"; private static final String VENDOR = "Red Hat Inc."; private static final String PLUGIN_ID = "commands"; private static final String ENDPOINT_FORMAT = "systems/%s/agents/%s"; private static final String UNKNOWN_CREDS = "UNKNOWN:UNKNOWN"; private final WsClientCreator wsClientCreator; private final CredentialsCreator credsCreator; private final ConfigCreator configCreator; private final CountDownLatch socketConnectLatch; private WebSocketClientFacade wsClient; private boolean isActive; private StorageCredentials creds; private PluginConfiguration config; private ReceiverRegistry receiverReg; @Reference private SystemID systemId; @Reference private WriterID agentId; @Reference private CommonPaths paths; @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, ConfigCreator configCreator, CountDownLatch socketConnectLatch) { super(NAME, DESCRIPTION, VENDOR); this.wsClientCreator = creator; this.credsCreator = credsCreator; this.configCreator = configCreator; this.socketConnectLatch = socketConnectLatch; } @Override public boolean activate() { if (!isActive) { // sets wsSocket and socket instance variables isActive = connectWsClient(); } return isActive; } @Override public boolean deactivate() { if (!isActive) { // nothing to do return true; } if (wsClient != null) { wsClient.stop(); } isActive = false; return true; } @Override public boolean isActive() { return isActive; } @Override public int getOrderValue() { return Ordered.ORDER_FIRST + 32; } @Activate protected void componentActivated(BundleContext ctx) { Version version = new Version(ctx.getBundle()); super.setVersion(version.getVersionInfo()); creds = credsCreator.create(paths); config = configCreator.createConfig(commandInfo); try { wsClient = wsClientCreator.createClient(sslConfig); wsClient.start(); } catch (Exception e) { logger.log(Level.WARNING, "Failed to start websocket client. Reason: " + e.getMessage()); } receiverReg = new ReceiverRegistry(ctx); } @Deactivate protected void noOp() { /* * Map unused DS deactivate method to this NOOP method to prevent it * from trying to use Backend.activate/deactivate and giving an error * about them being incompatible. */ } /** * * @return {@code true} if and only if connection was successfully made */ private boolean connectWsClient() { boolean expired = false; try { URI microserviceURI = config.getGatewayURL(); String agent = agentId.getWriterID(); 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(); 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); } catch (IOException | InterruptedException e) { logger.warning("Failed to connect to endpoint. Reason: " + e.getMessage()); logger.log(Level.FINE, "Failed to connect to endpoint", e); return false; } if (expired) { logger.warning("Did not receive connect event from endpoint"); return false; } else { logger.fine("Successfully connected agent web socket"); return true; } } 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(); String userpassword; if (username == null || username.isEmpty() || pwdChar == null) { logger.warning("No credentials specified in " + paths.getUserAgentAuthConfigFile() + ". The connection will fail."); userpassword = UNKNOWN_CREDS; } else { 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); } } }