Skip to content

Commit

Permalink
Merge pull request #52 from jpkrohling/HAWKULAR-265-HostSynonyms
Browse files Browse the repository at this point in the history
HAWKULAR-265 - Better solution for the host synonyms
  • Loading branch information
pilhuhn committed Oct 14, 2015
2 parents 3d3924b + 887fb58 commit 5685e42
Show file tree
Hide file tree
Showing 7 changed files with 456 additions and 19 deletions.
27 changes: 27 additions & 0 deletions common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,33 @@
<artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-processor</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
package org.hawkular.accounts.common;

import java.io.StringReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
Expand All @@ -44,7 +40,6 @@ public class ApplicationResources {
private String serverUrl;
private String resourceName;
private String secret;
private Set<String> hostSynonyms;

public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
Expand Down Expand Up @@ -90,16 +85,6 @@ public String getResourceNameSecret() {
return secret;
}

@Produces @HostSynonyms
public Set<String> getHostSynonyms() {
if (hostSynonyms == null) {
String synonyms = System.getProperty("org.hawkular.accounts.host.synonyms", "localhost,127.0.0.1,0.0.0.0");
hostSynonyms = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(synonyms.split(","))));
}

return hostSynonyms;
}

private void parseRealmConfiguration() {
JsonReader jsonReader = Json.createReader(new StringReader(getRealmConfiguration()));
JsonObject configurationJson = jsonReader.readObject();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.
*/
package org.hawkular.accounts.common;

/**
* @author Juraci Paixão Kröhling
*/
public interface AuthServerHostSynonymService {
/**
* Determines if the given host is a synonym for any of the hosts that our auth server runs on. The rules are:
* <ul>
* <li>
* If the system property org.hawkular.accounts.auth.host.synonyms is specified, then the host is matched
* against this list. This list should include all IPs and hostnames that are considered synonyms.
* </li>
* <li>
* If the system property is not set, we assume that the auth server is running on the same
* application server instance as Hawkular. We then build a list of local IPs. If the parameter
* {@param host} is an IP address, it's matched with this list. If it's a host, then the host is resolved
* to an IP, which is then matched with the list. The list of IPs is derived from the property
* jboss.bind.address as follows:
* <ul>
* <li>If it's not defined, then 127.0.0.1 is assumed (based on Wildfly's defaults)</li>
* <li>If it's 0.0.0.0, then all available NICs are queried and its IPs are added to a cache</li>
* <li>If it's not an IP, then the hostname is resolved into an IP</li>
* <li>If it's an IP, it's added as the single entry to the list.</li>
* </ul>
* </li>
* </ul>
*
* @param host the host to check if it's a synonym for the local host.
* @return whether or not the requested host is a synonym for the auth server being used by this instance
*/
boolean isHostSynonym(String host);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Set;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
Expand All @@ -39,8 +38,8 @@ public class TokenVerifier {
@Inject @RealmName
private String realm;

@Inject @HostSynonyms
private Set<String> hostSynonyms;
@Inject
private AuthServerHostSynonymService hostSynonymService;

@Inject
AuthServerRequestExecutor executor;
Expand All @@ -63,7 +62,7 @@ public String verify(String token) throws Exception {
URL backendUrl = new URL(accessToken.getIssuer());
URL baseUrlToCall = new URL(baseUrl);
if (!backendUrl.getHost().equalsIgnoreCase(baseUrlToCall.getHost())) {
if (hostSynonyms.contains(backendUrl.getHost())) {
if (hostSynonymService.isHostSynonym(backendUrl.getHost())) {
baseUrlToCall = new URL(
backendUrl.getProtocol(),
backendUrl.getHost(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.
*/
package org.hawkular.accounts.common.internal;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;

import org.hawkular.accounts.common.AuthServerHostSynonymService;

/**
* @author Juraci Paixão Kröhling
*/
@ApplicationScoped
public class AuthServerHostSynonymServiceImpl implements AuthServerHostSynonymService {
private MsgLogger logger = MsgLogger.LOGGER;
private static final String WILDCARD_ADDRESS = "0.0.0.0";
private static final String DEFAULT_ADDRESS = "127.0.0.1";

/**
* For this service, we have two algorithms: the first is just a match between the specified system property
* and the value that is to be checked. The second is here called as 'best effort', and
* we try our best to tell if a given host is a synonym for the local host.
*/
private boolean bestEffort = true;

private Set<String> hostSynonyms;
private Map<String, Boolean> hostnameCache;
private Set<InetAddress> localIPs;
private String synonyms;
private String boundToAddress;

public AuthServerHostSynonymServiceImpl() {
synonyms = System.getProperty("org.hawkular.accounts.auth.host.synonyms");
boundToAddress = System.getProperty("jboss.bind.address");
}

public AuthServerHostSynonymServiceImpl(String synonyms, String boundToAddress) {
this.synonyms = synonyms;
this.boundToAddress = boundToAddress;
}

@PostConstruct
void determineHostSynonyms() {
if (synonyms != null && !synonyms.isEmpty()) {
hostSynonyms = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(synonyms.split(","))));
bestEffort = false;
logger.listOfSynonymsProvided();
return;
}

// we have not found the system property, proceed with trying to get the local IPs
localIPs = new HashSet<>();
hostnameCache = new HashMap<>();

// if we are not bound to a specific address, then we are implicitly bound to 127.0.0.1
if (boundToAddress == null || boundToAddress.isEmpty()) {
boundToAddress = DEFAULT_ADDRESS;
}

// if we are bound to 0.0.0.0, then we are bound to all local addresses (not 100% true, but true for our case)
if (WILDCARD_ADDRESS.equals(boundToAddress)) {
logger.allLocalAddressesForSynonyms();
determineLocalAddresses();
try {
localIPs.add(InetAddress.getByName(WILDCARD_ADDRESS));
} catch (UnknownHostException e) {
// should *not* happen, but if it does, then let's just ignore... this is a "plus"
logger.cannotDetermineIPForWildcardHost(WILDCARD_ADDRESS);
}
return;
}

// at this point, we are either bound to a specific address, or to a hostname
// if we are bound to a hostname, let's try to find all IPs for it, otherwise, we just add the IP to the list
InetAddress[] addressForHost;
try {
addressForHost = InetAddress.getAllByName(boundToAddress);
} catch (UnknownHostException e) {
// how can that be? we are bound to a host which is not known to us? it happens, but should be very rare
logger.cannotDetermineIPForHost(WILDCARD_ADDRESS, e);
return;
}

Collections.addAll(localIPs, addressForHost);
}

@Override
public boolean isHostSynonym(String host) {
if (!bestEffort) {
return hostSynonyms.contains(host);
}

// first, check the cache
if (hostnameCache.containsKey(host)) {
return hostnameCache.get(host);
}

return checkNewHost(host);
}

boolean checkNewHost(String host) {
// not in the cache, so, proceed
InetAddress[] addressForHost;
try {
addressForHost = InetAddress.getAllByName(host);
} catch (UnknownHostException e) {
// no, this host is not known at all, it can't be us...
return false;
}

boolean found = false;
for (InetAddress address : addressForHost) {
if (localIPs.contains(address)) {
found = true;
}
}

hostnameCache.put(host, found);
return found;
}

void determineLocalAddresses() {
// Networking is a complex matter, and I'm not even trying to get this algorithm into a state where it will
// be correct most of the times, rather, I'm trying to provide a good out-of-the-box experience.
// For production environments, we should really try to document somewhere that admins *should* set the
// system property. The reason is: on production machines, we might or might not want to assume that some
// NICs and/or virtual interfaces are synonyms. One situation where we *don't* want is for bridged interfaces
// (like docker ones), which usually refers to a container, not the host. Other situations that we don't even
// try to handle here: firewalls and frontend-proxies. For instance, a company might have a load balancer or
// frontend-proxy with a public IP address and hostname, but all we (backend) see is a list of private
// addresses.
Enumeration<NetworkInterface> networkInterfaceEnumeration = getNetworkInterfaces();

while (networkInterfaceEnumeration.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaceEnumeration.nextElement();
Enumeration<InetAddress> addressEnumeration = networkInterface.getInetAddresses();
while (addressEnumeration.hasMoreElements()) {
InetAddress address = addressEnumeration.nextElement();
localIPs.add(address);
}
}
}

Set<InetAddress> getLocalIPs() {
if (null == localIPs) {
return null;
}
return Collections.unmodifiableSet(localIPs);
}

Enumeration<NetworkInterface> getNetworkInterfaces() {
try {
return NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
// uh-oh, we are in trouble... we don't have the system property and we are bound to "all" addresses
// we don't have much choice other than to re-throw it as a runtime exception
logger.cannotDetermineLocalIPs(e);
throw new RuntimeException(e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.
*/
package org.hawkular.accounts.common.internal;

import org.jboss.logging.Logger;
import org.jboss.logging.annotations.Cause;
import org.jboss.logging.annotations.LogMessage;
import org.jboss.logging.annotations.Message;
import org.jboss.logging.annotations.MessageLogger;
import org.jboss.logging.annotations.ValidIdRange;

/**
* @author Juraci Paixão Kröhling
*/
@MessageLogger(projectCode = "HAWKACC")
@ValidIdRange(min = 150000, max = 159999)
public interface MsgLogger {
MsgLogger LOGGER = Logger.getMessageLogger(MsgLogger.class, MsgLogger.class.getPackage().getName());

@LogMessage(level = Logger.Level.DEBUG)
@Message(id = 150000, value = "List of host synonyms provided. Using the list instead of guessing.")
void listOfSynonymsProvided();

@LogMessage(level = Logger.Level.DEBUG)
@Message(id = 150001, value = "Bound to wildcard address 0.0.0.0, getting a list of all local IPs for Synonyms")
void allLocalAddressesForSynonyms();

@LogMessage(level = Logger.Level.DEBUG)
@Message(id = 150002, value = "Could not process what's the IP for the wildcard host: [%s]")
void cannotDetermineIPForWildcardHost(String host);

@LogMessage(level = Logger.Level.WARN)
@Message(id = 150003, value = "Could not process what's the IP for the host: [%s]")
void cannotDetermineIPForHost(String host, @Cause Throwable t);

@LogMessage(level = Logger.Level.FATAL)
@Message(id = 150004, value = "Could not process what are our IPs. Host synonyms will *not* work properly")
void cannotDetermineLocalIPs(@Cause Throwable t);

}

0 comments on commit 5685e42

Please sign in to comment.